This commit is contained in:
parent
1191fbe6c2
commit
1e748a64d7
@ -21,6 +21,7 @@ from lemur.listeners.views import mod as listeners_bp
|
||||
from lemur.certificates.views import mod as certificates_bp
|
||||
from lemur.status.views import mod as status_bp
|
||||
from lemur.plugins.views import mod as plugins_bp
|
||||
from lemur.notifications.views import mod as notifications_bp
|
||||
|
||||
LEMUR_BLUEPRINTS = (
|
||||
users_bp,
|
||||
@ -34,6 +35,7 @@ LEMUR_BLUEPRINTS = (
|
||||
certificates_bp,
|
||||
status_bp,
|
||||
plugins_bp,
|
||||
notifications_bp,
|
||||
)
|
||||
|
||||
|
||||
|
@ -15,7 +15,7 @@ from lemur.authorities.models import Authority
|
||||
from lemur.roles import service as role_service
|
||||
|
||||
from lemur.roles.models import Role
|
||||
import lemur.certificates.service as cert_service
|
||||
from lemur.certificates.models import Certificate
|
||||
|
||||
from lemur.plugins.base import plugins
|
||||
|
||||
@ -42,10 +42,6 @@ def create(kwargs):
|
||||
"""
|
||||
Create a new authority.
|
||||
|
||||
:param name: name of the authority
|
||||
:param roles: roles that are allowed to use this authority
|
||||
:param options: available options for authority
|
||||
:param description:
|
||||
:rtype : Authority
|
||||
:return:
|
||||
"""
|
||||
@ -55,7 +51,9 @@ def create(kwargs):
|
||||
kwargs['creator'] = g.current_user.email
|
||||
cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs)
|
||||
|
||||
cert = cert_service.save_cert(cert_body, None, intermediate, [])
|
||||
cert = Certificate(cert_body, chain=intermediate)
|
||||
cert.owner = kwargs['ownerEmail']
|
||||
cert.description = "This is the ROOT certificate for the {0} certificate authority".format(kwargs.get('caName'))
|
||||
cert.user = g.current_user
|
||||
|
||||
# we create and attach any roles that the issuer gives us
|
||||
|
@ -13,16 +13,17 @@ from cryptography.hazmat.backends import default_backend
|
||||
from flask import current_app
|
||||
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean
|
||||
from sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean
|
||||
|
||||
from sqlalchemy_utils import EncryptedType
|
||||
|
||||
from lemur.database import db
|
||||
from lemur.plugins.base import plugins
|
||||
|
||||
from lemur.domains.models import Domain
|
||||
|
||||
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE
|
||||
from lemur.models import certificate_associations, certificate_destination_associations
|
||||
from lemur.models import certificate_associations, certificate_destination_associations, certificate_notification_associations
|
||||
|
||||
|
||||
def create_name(issuer, not_before, not_after, subject, san):
|
||||
@ -147,7 +148,7 @@ def cert_get_issuer(cert):
|
||||
"""
|
||||
delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
|
||||
try:
|
||||
issuer = str(cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)[0].value)
|
||||
issuer = str(cert.issuer.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)[0].value)
|
||||
return issuer.translate(None, delchars)
|
||||
except Exception as e:
|
||||
current_app.logger.error("Unable to get issuer! {0}".format(e))
|
||||
@ -203,8 +204,6 @@ class Certificate(db.Model):
|
||||
owner = Column(String(128))
|
||||
body = Column(Text())
|
||||
private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY')))
|
||||
challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) # TODO deprecate
|
||||
csr_config = Column(Text()) # TODO deprecate
|
||||
status = Column(String(128))
|
||||
deleted = Column(Boolean, index=True)
|
||||
name = Column(String(128))
|
||||
@ -221,7 +220,8 @@ class Certificate(db.Model):
|
||||
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
||||
user_id = Column(Integer, ForeignKey('users.id'))
|
||||
authority_id = Column(Integer, ForeignKey('authorities.id'))
|
||||
accounts = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
|
||||
notifications = relationship("Notification", secondary=certificate_notification_associations, backref='certificate')
|
||||
destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
|
||||
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
|
||||
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')
|
||||
|
||||
@ -272,3 +272,10 @@ class Certificate(db.Model):
|
||||
|
||||
def as_dict(self):
|
||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||
|
||||
|
||||
@event.listens_for(Certificate.destinations, 'append')
|
||||
def update_destinations(target, value, initiator):
|
||||
destination_plugin = plugins.get(value.plugin_name)
|
||||
|
||||
destination_plugin.upload(target.body, target.private_key, target.chain, value.options)
|
||||
|
@ -15,6 +15,7 @@ from lemur.plugins.base import plugins
|
||||
from lemur.certificates.models import Certificate
|
||||
|
||||
from lemur.destinations.models import Destination
|
||||
from lemur.notifications.models import Notification
|
||||
from lemur.authorities.models import Authority
|
||||
|
||||
from lemur.roles.models import Role
|
||||
@ -75,7 +76,7 @@ def find_duplicates(cert_body):
|
||||
return Certificate.query.filter_by(body=cert_body).all()
|
||||
|
||||
|
||||
def update(cert_id, owner, active):
|
||||
def update(cert_id, owner, description, active, destinations, notifications):
|
||||
"""
|
||||
Updates a certificate.
|
||||
|
||||
@ -87,6 +88,11 @@ def update(cert_id, owner, active):
|
||||
cert = get(cert_id)
|
||||
cert.owner = owner
|
||||
cert.active = active
|
||||
cert.description = description
|
||||
|
||||
database.update_list(cert, 'notifications', Notification, notifications)
|
||||
database.update_list(cert, 'destinations', Destination, destinations)
|
||||
|
||||
return database.update(cert)
|
||||
|
||||
|
||||
@ -106,7 +112,8 @@ def mint(issuer_options):
|
||||
issuer_options['creator'] = g.user.email
|
||||
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
|
||||
|
||||
cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('destinations'))
|
||||
cert = Certificate(cert_body, private_key, cert_chain)
|
||||
|
||||
cert.user = g.user
|
||||
cert.authority = authority
|
||||
database.update(cert)
|
||||
@ -139,43 +146,25 @@ def import_certificate(**kwargs):
|
||||
if kwargs.get('user'):
|
||||
cert.user = kwargs.get('user')
|
||||
|
||||
if kwargs.get('destination'):
|
||||
cert.destinations.append(kwargs.get('destination'))
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
|
||||
cert = database.create(cert)
|
||||
return cert
|
||||
|
||||
|
||||
def save_cert(cert_body, private_key, cert_chain, destinations):
|
||||
"""
|
||||
Determines if the certificate needs to be uploaded to AWS or other services.
|
||||
|
||||
:param cert_body:
|
||||
:param private_key:
|
||||
:param cert_chain:
|
||||
:param destinations:
|
||||
"""
|
||||
cert = Certificate(cert_body, private_key, cert_chain)
|
||||
|
||||
# we should save them to any destination that is requested
|
||||
for destination in destinations:
|
||||
destination_plugin = plugins.get(destination['plugin']['slug'])
|
||||
destination_plugin.upload(cert, private_key, cert_chain, destination['plugin']['pluginOptions'])
|
||||
|
||||
return cert
|
||||
|
||||
|
||||
def upload(**kwargs):
|
||||
"""
|
||||
Allows for pre-made certificates to be imported into Lemur.
|
||||
"""
|
||||
cert = save_cert(
|
||||
cert = Certificate(
|
||||
kwargs.get('public_cert'),
|
||||
kwargs.get('private_key'),
|
||||
kwargs.get('intermediate_cert'),
|
||||
kwargs.get('destinations')
|
||||
)
|
||||
|
||||
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
|
||||
cert.owner = kwargs['owner']
|
||||
cert = database.create(cert)
|
||||
g.user.certificates.append(cert)
|
||||
@ -189,10 +178,18 @@ def create(**kwargs):
|
||||
cert, private_key, cert_chain = mint(kwargs)
|
||||
|
||||
cert.owner = kwargs['owner']
|
||||
|
||||
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
||||
|
||||
database.create(cert)
|
||||
cert.description = kwargs['description']
|
||||
g.user.certificates.append(cert)
|
||||
database.update(g.user)
|
||||
|
||||
# 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.
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
database.update(cert)
|
||||
return cert
|
||||
|
||||
|
||||
@ -207,6 +204,7 @@ def render(args):
|
||||
|
||||
time_range = args.pop('time_range')
|
||||
destination_id = args.pop('destination_id')
|
||||
notification_id = args.pop('notification_id', None)
|
||||
show = args.pop('show')
|
||||
# owner = args.pop('owner')
|
||||
# creator = args.pop('creator') # TODO we should enabling filtering by owner
|
||||
@ -248,6 +246,9 @@ def render(args):
|
||||
if destination_id:
|
||||
query = query.filter(Certificate.destinations.any(Destination.id == destination_id))
|
||||
|
||||
if notification_id:
|
||||
query = query.filter(Certificate.notifications.any(Notification.id == notification_id))
|
||||
|
||||
if time_range:
|
||||
to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD')
|
||||
now = arrow.now().format('YYYY-MM-DD')
|
||||
@ -284,11 +285,17 @@ def create_csr(csr_config):
|
||||
x509.BasicConstraints(ca=False, path_length=None), critical=True,
|
||||
)
|
||||
|
||||
# for k, v in csr_config.get('extensions', {}).items():
|
||||
# if k == 'subAltNames':
|
||||
# builder = builder.add_extension(
|
||||
# x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True,
|
||||
# )
|
||||
for k, v in csr_config.get('extensions', {}).items():
|
||||
if k == 'subAltNames':
|
||||
# map types to their x509 objects
|
||||
general_names = []
|
||||
for name in v['names']:
|
||||
if name['nameType'] == 'DNSName':
|
||||
general_names.append(x509.DNSName(name['value']))
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectAlternativeName(general_names), critical=True
|
||||
)
|
||||
|
||||
# TODO support more CSR options, none of the authorities support these atm
|
||||
# builder.add_extension(
|
||||
|
@ -271,7 +271,7 @@ class CertificatesList(AuthenticatedResource):
|
||||
"""
|
||||
self.reqparse.add_argument('extensions', type=dict, location='json')
|
||||
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('elbs', type=list, location='json')
|
||||
self.reqparse.add_argument('notifications', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('owner', type=str, location='json')
|
||||
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
|
||||
self.reqparse.add_argument('validityEnd', type=str, location='json') # TODO validate
|
||||
@ -329,7 +329,8 @@ class CertificatesUpload(AuthenticatedResource):
|
||||
"publicCert": "---Begin Public...",
|
||||
"intermediateCert": "---Begin Public...",
|
||||
"privateKey": "---Begin Private..."
|
||||
"destinations": []
|
||||
"destinations": [],
|
||||
"notifications": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@ -371,6 +372,7 @@ class CertificatesUpload(AuthenticatedResource):
|
||||
self.reqparse.add_argument('owner', type=str, required=True, location='json')
|
||||
self.reqparse.add_argument('publicCert', type=pem_str, required=True, dest='public_cert', location='json')
|
||||
self.reqparse.add_argument('destinations', type=list, default=[], dest='destinations', location='json')
|
||||
self.reqparse.add_argument('notifications', type=list, default=[], dest='notifications', 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')
|
||||
|
||||
@ -523,6 +525,8 @@ class Certificates(AuthenticatedResource):
|
||||
{
|
||||
"owner": "jimbob@example.com",
|
||||
"active": false
|
||||
"notifications": [],
|
||||
"destinations": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@ -549,7 +553,7 @@ class Certificates(AuthenticatedResource):
|
||||
"notBefore": "2015-06-05T17:09:39",
|
||||
"notAfter": "2015-06-10T17:09:39",
|
||||
"cn": "example.com",
|
||||
"status": "unknown"
|
||||
"status": "unknown",
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
@ -558,6 +562,9 @@ class Certificates(AuthenticatedResource):
|
||||
"""
|
||||
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=list, default=[], location='json')
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
cert = service.get(certificate_id)
|
||||
@ -565,13 +572,96 @@ class Certificates(AuthenticatedResource):
|
||||
permission = UpdateCertificatePermission(certificate_id, hasattr(role, 'id'))
|
||||
|
||||
if permission.can():
|
||||
return service.update(certificate_id, args['owner'], args['active'])
|
||||
return service.update(
|
||||
certificate_id,
|
||||
args['owner'],
|
||||
args['description'],
|
||||
args['active'],
|
||||
args['destinations'],
|
||||
args['notifications']
|
||||
)
|
||||
|
||||
return dict(message='You are not authorized to update this certificate'), 403
|
||||
|
||||
|
||||
class NotificationCertificatesList(AuthenticatedResource):
|
||||
""" Defines the 'certificates' endpoint """
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(NotificationCertificatesList, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, notification_id):
|
||||
"""
|
||||
.. http:get:: /notifications/1/certificates
|
||||
|
||||
The current list of certificates for a given notification
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /notifications/1/certificates HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "cert1",
|
||||
"description": "this is cert1",
|
||||
"bits": 2048,
|
||||
"deleted": false,
|
||||
"issuer": "ExampeInc.",
|
||||
"serial": "123450",
|
||||
"chain": "-----Begin ...",
|
||||
"body": "-----Begin ...",
|
||||
"san": true,
|
||||
"owner": 'bob@example.com",
|
||||
"active": true,
|
||||
"notBefore": "2015-06-05T17:09:39",
|
||||
"notAfter": "2015-06-10T17:09:39",
|
||||
"cn": "example.com",
|
||||
"status": "unknown"
|
||||
}
|
||||
]
|
||||
"total": 1
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
:query filter: key value pair. format is k=v;
|
||||
:query limit: limit number. default is 10
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
:statuscode 403: unauthenticated
|
||||
"""
|
||||
parser = paginated_parser.copy()
|
||||
parser.add_argument('timeRange', type=int, dest='time_range', location='args')
|
||||
parser.add_argument('owner', type=bool, location='args')
|
||||
parser.add_argument('id', type=str, location='args')
|
||||
parser.add_argument('active', type=bool, location='args')
|
||||
parser.add_argument('destinationId', type=int, dest="destination_id", location='args')
|
||||
parser.add_argument('creator', type=str, location='args')
|
||||
parser.add_argument('show', type=str, location='args')
|
||||
|
||||
args = parser.parse_args()
|
||||
args['notification_id'] = notification_id
|
||||
return service.render(args)
|
||||
|
||||
api.add_resource(CertificatesList, '/certificates', endpoint='certificates')
|
||||
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
||||
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
||||
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
||||
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
||||
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates')
|
||||
|
@ -14,7 +14,6 @@ from lemur.auth.service import AuthenticatedResource
|
||||
from lemur.auth.permissions import admin_permission
|
||||
from lemur.common.utils import paginated_parser, marshal_items
|
||||
|
||||
from lemur.plugins.views import FIELDS as PLUGIN_FIELDS
|
||||
|
||||
mod = Blueprint('destinations', __name__)
|
||||
api = Api(mod)
|
||||
@ -22,7 +21,8 @@ api = Api(mod)
|
||||
|
||||
FIELDS = {
|
||||
'description': fields.String,
|
||||
'plugin': fields.Nested(PLUGIN_FIELDS, attribute='plugin'),
|
||||
'destinationOptions': fields.Raw(attribute='options'),
|
||||
'pluginName': fields.String(attribute='plugin_name'),
|
||||
'label': fields.String,
|
||||
'id': fields.Integer,
|
||||
}
|
||||
@ -60,19 +60,23 @@ class DestinationsList(AuthenticatedResource):
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
]
|
||||
"total": 2
|
||||
"destinationOptions": [
|
||||
{
|
||||
"name": "accountNumber",
|
||||
"required": true,
|
||||
"value": 111111111112,
|
||||
"helpMessage": "Must be a valid AWS account number!",
|
||||
"validation": "/^[0-9]{12,12}$/",
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"pluginName": "aws-destination",
|
||||
"id": 3,
|
||||
"description": "test",
|
||||
"label": "test"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
@ -104,9 +108,20 @@ class DestinationsList(AuthenticatedResource):
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1,
|
||||
"comments": "this is a thing"
|
||||
"destinationOptions": [
|
||||
{
|
||||
"name": "accountNumber",
|
||||
"required": true,
|
||||
"value": 111111111112,
|
||||
"helpMessage": "Must be a valid AWS account number!",
|
||||
"validation": "/^[0-9]{12,12}$/",
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"pluginName": "aws-destination",
|
||||
"id": 3,
|
||||
"description": "test",
|
||||
"label": "test"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@ -118,15 +133,24 @@ class DestinationsList(AuthenticatedResource):
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
"destinationOptions": [
|
||||
{
|
||||
"name": "accountNumber",
|
||||
"required": true,
|
||||
"value": 111111111112,
|
||||
"helpMessage": "Must be a valid AWS account number!",
|
||||
"validation": "/^[0-9]{12,12}$/",
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"pluginName": "aws-destination",
|
||||
"id": 3,
|
||||
"description": "test",
|
||||
"label": "test"
|
||||
}
|
||||
|
||||
:arg accountNumber: aws account number
|
||||
:arg label: human readable account label
|
||||
:arg comments: some description about the account
|
||||
:arg description: some description about the account
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
@ -167,10 +191,20 @@ class Destinations(AuthenticatedResource):
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
"destinationOptions": [
|
||||
{
|
||||
"name": "accountNumber",
|
||||
"required": true,
|
||||
"value": 111111111112,
|
||||
"helpMessage": "Must be a valid AWS account number!",
|
||||
"validation": "/^[0-9]{12,12}$/",
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"pluginName": "aws-destination",
|
||||
"id": 3,
|
||||
"description": "test",
|
||||
"label": "test"
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
@ -194,6 +228,22 @@ class Destinations(AuthenticatedResource):
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"destinationOptions": [
|
||||
{
|
||||
"name": "accountNumber",
|
||||
"required": true,
|
||||
"value": 111111111112,
|
||||
"helpMessage": "Must be a valid AWS account number!",
|
||||
"validation": "/^[0-9]{12,12}$/",
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"pluginName": "aws-destination",
|
||||
"id": 3,
|
||||
"description": "test",
|
||||
"label": "test"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
@ -204,24 +254,34 @@ class Destinations(AuthenticatedResource):
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "labelChanged",
|
||||
"comments": "this is a thing"
|
||||
"destinationOptions": [
|
||||
{
|
||||
"name": "accountNumber",
|
||||
"required": true,
|
||||
"value": 111111111112,
|
||||
"helpMessage": "Must be a valid AWS account number!",
|
||||
"validation": "/^[0-9]{12,12}$/",
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"pluginName": "aws-destination",
|
||||
"id": 3,
|
||||
"description": "test",
|
||||
"label": "test"
|
||||
}
|
||||
|
||||
:arg accountNumber: aws account number
|
||||
:arg label: human readable account label
|
||||
:arg comments: some description about the account
|
||||
:arg description: some description about the account
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
self.reqparse.add_argument('label', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('pluginOptions', type=dict, location='json', required=True)
|
||||
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
|
||||
self.reqparse.add_argument('description', type=str, location='json')
|
||||
|
||||
args = self.reqparse.parse_args()
|
||||
return service.update(destination_id, args['label'], args['options'], args['description'])
|
||||
return service.update(destination_id, args['label'], args['plugin']['pluginOptions'], args['description'])
|
||||
|
||||
@admin_permission.require(http_exception=403)
|
||||
def delete(self, destination_id):
|
||||
@ -257,6 +317,28 @@ class CertificateDestinations(AuthenticatedResource):
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"destinationOptions": [
|
||||
{
|
||||
"name": "accountNumber",
|
||||
"required": true,
|
||||
"value": 111111111112,
|
||||
"helpMessage": "Must be a valid AWS account number!",
|
||||
"validation": "/^[0-9]{12,12}$/",
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"pluginName": "aws-destination",
|
||||
"id": 3,
|
||||
"description": "test",
|
||||
"label": "test"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
|
@ -32,6 +32,8 @@ from lemur.destinations.models import Destination # noqa
|
||||
from lemur.domains.models import Domain # noqa
|
||||
from lemur.elbs.models import ELB # noqa
|
||||
from lemur.listeners.models import Listener # noqa
|
||||
from lemur.notifications.models import Notification # noqa
|
||||
|
||||
|
||||
manager = Manager(create_app)
|
||||
manager.add_option('-c', '--config', dest='config')
|
||||
|
@ -25,6 +25,12 @@ certificate_destination_associations = db.Table('certificate_destination_associa
|
||||
ForeignKey('certificates.id', ondelete='cascade'))
|
||||
)
|
||||
|
||||
certificate_notification_associations = db.Table('certificate_notification_associations',
|
||||
Column('notification_id', Integer,
|
||||
ForeignKey('notifications.id', ondelete='cascade')),
|
||||
Column('certificate_id', Integer,
|
||||
ForeignKey('certificates.id', ondelete='cascade'))
|
||||
)
|
||||
roles_users = db.Table('roles_users',
|
||||
Column('user_id', Integer, ForeignKey('users.id')),
|
||||
Column('role_id', Integer, ForeignKey('roles.id'))
|
||||
|
@ -1,218 +0,0 @@
|
||||
"""
|
||||
.. module: lemur.notifications
|
||||
: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>
|
||||
|
||||
"""
|
||||
import ssl
|
||||
import socket
|
||||
|
||||
import arrow
|
||||
import boto.ses
|
||||
|
||||
from flask import current_app
|
||||
from flask_mail import Message
|
||||
|
||||
from lemur import database
|
||||
from lemur.certificates.models import Certificate
|
||||
from lemur.domains.models import Domain
|
||||
|
||||
from lemur.templates.config import env
|
||||
from lemur.extensions import smtp_mail
|
||||
|
||||
|
||||
NOTIFICATION_INTERVALS = [30, 15, 5, 2]
|
||||
|
||||
|
||||
def _get_domain_certificate(name):
|
||||
"""
|
||||
Fetch the SSL certificate currently hosted at a given domain (if any) and
|
||||
compare it against our all of our know certificates to determine if a new
|
||||
SSL certificate has already been deployed
|
||||
|
||||
:param name:
|
||||
:return:
|
||||
"""
|
||||
query = database.session_query(Certificate)
|
||||
try:
|
||||
pub_key = ssl.get_server_certificate((name, 443))
|
||||
return query.filter(Certificate.body == pub_key.strip()).first()
|
||||
|
||||
except socket.gaierror as e:
|
||||
current_app.logger.info(str(e))
|
||||
|
||||
|
||||
def _find_superseded(domains):
|
||||
"""
|
||||
Here we try to fetch any domain in the certificate to see if we can resolve it
|
||||
and to try and see if it is currently serving the certificate we are
|
||||
alerting on
|
||||
|
||||
:param domains:
|
||||
:return:
|
||||
"""
|
||||
query = database.session_query(Certificate)
|
||||
ss_list = []
|
||||
for domain in domains:
|
||||
dc = _get_domain_certificate(domain.name)
|
||||
if dc:
|
||||
ss_list.append(dc)
|
||||
current_app.logger.info("Trying to resolve {0}".format(domain.name))
|
||||
|
||||
query = query.filter(Certificate.domains.any(Domain.name.in_([x.name for x in domains])))
|
||||
query = query.filter(Certificate.active == True) # noqa
|
||||
query = query.filter(Certificate.not_after >= arrow.utcnow().format('YYYY-MM-DD'))
|
||||
ss_list.extend(query.all())
|
||||
|
||||
return ss_list
|
||||
|
||||
|
||||
def send_expiration_notifications():
|
||||
"""
|
||||
This function will check for upcoming certificate expiration,
|
||||
and send out notification emails at given intervals.
|
||||
"""
|
||||
notifications = 0
|
||||
certs = _get_expiring_certs()
|
||||
|
||||
alerts = []
|
||||
for cert in certs:
|
||||
if _is_eligible_for_notifications(cert):
|
||||
data = _get_message_data(cert)
|
||||
recipients = _get_message_recipients(cert)
|
||||
alerts.append((data, recipients))
|
||||
|
||||
roll_ups = _create_roll_ups(alerts)
|
||||
|
||||
for messages, recipients in roll_ups:
|
||||
notifications += 1
|
||||
send("Certificate Expiration", dict(messages=messages), 'event', recipients)
|
||||
|
||||
print notifications
|
||||
current_app.logger.info("Lemur has sent {0} certification notifications".format(notifications))
|
||||
|
||||
|
||||
def _get_message_recipients(cert):
|
||||
"""
|
||||
Determine who the recipients of the certificate expiration should be
|
||||
|
||||
:param cert:
|
||||
:return:
|
||||
"""
|
||||
recipients = []
|
||||
if current_app.config.get('SECURITY_TEAM_EMAIL'):
|
||||
recipients.extend(current_app.config.get('SECURITY_TEAM_EMAIL'))
|
||||
|
||||
recipients.append(cert.owner)
|
||||
|
||||
if cert.user:
|
||||
recipients.append(cert.user.email)
|
||||
return list(set(recipients))
|
||||
|
||||
|
||||
def _get_message_data(cert):
|
||||
"""
|
||||
Parse our the certification information needed for our notification
|
||||
|
||||
:param cert:
|
||||
:return:
|
||||
"""
|
||||
cert_dict = cert.as_dict()
|
||||
cert_dict['domains'] = [x .name for x in cert.domains]
|
||||
cert_dict['superseded'] = list(set([x.name for x in _find_superseded(cert.domains) if cert.name != x]))
|
||||
return cert_dict
|
||||
|
||||
|
||||
def _get_expiring_certs(outlook=30):
|
||||
"""
|
||||
Find all the certificates expiring within a given outlook
|
||||
|
||||
:param outlook: int days to look forward
|
||||
:return:
|
||||
"""
|
||||
now = arrow.utcnow()
|
||||
|
||||
query = database.session_query(Certificate)
|
||||
attr = Certificate.not_after
|
||||
|
||||
# get all certs expiring in the next 30 days
|
||||
to = now.replace(days=+outlook).format('YYYY-MM-DD')
|
||||
|
||||
certs = []
|
||||
for cert in query.filter(attr <= to).filter(attr >= now.format('YYYY-MM-DD')).all():
|
||||
if _is_eligible_for_notifications(cert):
|
||||
certs.append(cert)
|
||||
return certs
|
||||
|
||||
|
||||
def _is_eligible_for_notifications(cert, intervals=None):
|
||||
"""
|
||||
Determine if notifications for a given certificate should
|
||||
currently be sent
|
||||
|
||||
:param cert:
|
||||
:param intervals: list of days to alert on
|
||||
:return:
|
||||
"""
|
||||
now = arrow.utcnow()
|
||||
if cert.active:
|
||||
days = (cert.not_after - now.naive).days
|
||||
|
||||
if not intervals:
|
||||
intervals = NOTIFICATION_INTERVALS
|
||||
|
||||
if days in intervals:
|
||||
return cert
|
||||
|
||||
|
||||
def _create_roll_ups(messages):
|
||||
"""
|
||||
Take all of the messages that should be sent and provide
|
||||
a roll up to the same set if the recipients are the same
|
||||
|
||||
:param messages:
|
||||
"""
|
||||
roll_ups = []
|
||||
for message_data, recipients in messages:
|
||||
for m, r in roll_ups:
|
||||
if r == recipients:
|
||||
m.append(message_data)
|
||||
current_app.logger.info(
|
||||
"Sending email expiration alert about {0} to {1}".format(
|
||||
message_data['name'], ",".join(recipients)))
|
||||
break
|
||||
else:
|
||||
roll_ups.append(([message_data], recipients))
|
||||
return roll_ups
|
||||
|
||||
|
||||
def send(subject, data, email_type, recipients):
|
||||
"""
|
||||
Configures all Lemur email messaging
|
||||
|
||||
:param subject:
|
||||
:param data:
|
||||
:param email_type:
|
||||
:param recipients:
|
||||
"""
|
||||
# jinja template depending on type
|
||||
template = env.get_template('{}.html'.format(email_type))
|
||||
body = template.render(**data)
|
||||
|
||||
s_type = current_app.config.get("LEMUR_EMAIL_SENDER").lower()
|
||||
if s_type == 'ses':
|
||||
conn = boto.connect_ses()
|
||||
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, recipients, format='html')
|
||||
|
||||
elif s_type == 'smtp':
|
||||
msg = Message(subject, recipients=recipients)
|
||||
msg.body = "" # kinda a weird api for sending html emails
|
||||
msg.html = body
|
||||
smtp_mail.send(msg)
|
||||
|
||||
else:
|
||||
current_app.logger.error("No mail carrier specified, notification emails were not able to be sent!")
|
29
lemur/notifications/models.py
Normal file
29
lemur/notifications/models.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
.. module: lemur.notifications.models
|
||||
: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 sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Integer, String, Column, Boolean, Text
|
||||
from sqlalchemy_utils import JSONType
|
||||
|
||||
from lemur.database import db
|
||||
from lemur.plugins.base import plugins
|
||||
from lemur.models import certificate_notification_associations
|
||||
|
||||
|
||||
class Notification(db.Model):
|
||||
__tablename__ = 'notifications'
|
||||
id = Column(Integer, primary_key=True)
|
||||
label = Column(String(128))
|
||||
description = Column(Text())
|
||||
options = Column(JSONType)
|
||||
active = Column(Boolean, default=True)
|
||||
plugin_name = Column(String(32))
|
||||
certificates = relationship("Certificate", secondary=certificate_notification_associations, passive_deletes=True, backref="notification", cascade='all,delete')
|
||||
|
||||
@property
|
||||
def plugin(self):
|
||||
return plugins.get(self.plugin_name)
|
254
lemur/notifications/service.py
Normal file
254
lemur/notifications/service.py
Normal file
@ -0,0 +1,254 @@
|
||||
"""
|
||||
.. module: lemur.notifications
|
||||
: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>
|
||||
|
||||
"""
|
||||
import ssl
|
||||
import socket
|
||||
|
||||
import arrow
|
||||
|
||||
from flask import current_app
|
||||
from lemur import database
|
||||
from lemur.domains.models import Domain
|
||||
from lemur.notifications.models import Notification
|
||||
from lemur.certificates.models import Certificate
|
||||
|
||||
from lemur.certificates import service as cert_service
|
||||
|
||||
from lemur.plugins.base import plugins
|
||||
|
||||
|
||||
def _get_message_data(cert):
|
||||
"""
|
||||
Parse our the certification information needed for our notification
|
||||
|
||||
:param cert:
|
||||
:return:
|
||||
"""
|
||||
cert_dict = cert.as_dict()
|
||||
cert_dict['creator'] = cert.user.email
|
||||
cert_dict['domains'] = [x .name for x in cert.domains]
|
||||
cert_dict['superseded'] = list(set([x.name for x in find_superseded(cert.domains) if cert.name != x]))
|
||||
return cert_dict
|
||||
|
||||
|
||||
def _deduplicate(messages):
|
||||
"""
|
||||
Take all of the messages that should be sent and provide
|
||||
a roll up to the same set if the recipients are the same
|
||||
"""
|
||||
roll_ups = []
|
||||
for targets, data in messages:
|
||||
for m, r in roll_ups:
|
||||
if r == targets:
|
||||
m.append(data)
|
||||
current_app.logger.info(
|
||||
"Sending expiration alert about {0} to {1}".format(
|
||||
data['name'], ",".join(targets)))
|
||||
break
|
||||
else:
|
||||
roll_ups.append(([data], targets, data.plugin_options))
|
||||
return roll_ups
|
||||
|
||||
|
||||
def send_expiration_notifications():
|
||||
"""
|
||||
This function will check for upcoming certificate expiration,
|
||||
and send out notification emails at given intervals.
|
||||
"""
|
||||
notifications = 0
|
||||
|
||||
for plugin_name, notifications in database.get_all(Notification, 'active', field='status').group_by(Notification.plugin_name):
|
||||
notifications += 1
|
||||
|
||||
messages = _deduplicate(notifications)
|
||||
plugin = plugins.get(plugin_name)
|
||||
|
||||
for data, targets, options in messages:
|
||||
plugin.send('expiration', data, targets, options)
|
||||
|
||||
current_app.logger.info("Lemur has sent {0} certification notifications".format(notifications))
|
||||
|
||||
|
||||
def get_domain_certificate(name):
|
||||
"""
|
||||
Fetch the SSL certificate currently hosted at a given domain (if any) and
|
||||
compare it against our all of our know certificates to determine if a new
|
||||
SSL certificate has already been deployed
|
||||
|
||||
:param name:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
pub_key = ssl.get_server_certificate((name, 443))
|
||||
return cert_service.find_duplicates(pub_key.strip())
|
||||
except socket.gaierror as e:
|
||||
current_app.logger.info(str(e))
|
||||
|
||||
|
||||
def find_superseded(domains):
|
||||
"""
|
||||
Here we try to fetch any domain in the certificate to see if we can resolve it
|
||||
and to try and see if it is currently serving the certificate we are
|
||||
alerting on.
|
||||
|
||||
:param domains:
|
||||
:return:
|
||||
"""
|
||||
query = database.session_query(Certificate)
|
||||
ss_list = []
|
||||
for domain in domains:
|
||||
dc = get_domain_certificate(domain.name)
|
||||
if dc:
|
||||
ss_list.append(dc)
|
||||
current_app.logger.info("Trying to resolve {0}".format(domain.name))
|
||||
|
||||
query = query.filter(Certificate.domains.any(Domain.name.in_([x.name for x in domains])))
|
||||
query = query.filter(Certificate.active == True) # noqa
|
||||
query = query.filter(Certificate.not_after >= arrow.utcnow().format('YYYY-MM-DD'))
|
||||
ss_list.extend(query.all())
|
||||
|
||||
return ss_list
|
||||
|
||||
|
||||
def _is_eligible_for_notifications(cert):
|
||||
"""
|
||||
Determine if notifications for a given certificate should
|
||||
currently be sent
|
||||
|
||||
:param cert:
|
||||
:return:
|
||||
"""
|
||||
now = arrow.utcnow()
|
||||
days = (cert.not_after - now.naive).days
|
||||
|
||||
for notification in cert.notifications:
|
||||
interval = notification.options['interval']
|
||||
unit = notification.options['unit']
|
||||
if unit == 'weeks':
|
||||
interval *= 7
|
||||
|
||||
elif unit == 'months':
|
||||
interval *= 30
|
||||
|
||||
elif unit == 'days': # it's nice to be explicit about the base unit
|
||||
pass
|
||||
|
||||
else:
|
||||
raise Exception("Invalid base unit for expiration interval: {0}".format(unit))
|
||||
|
||||
if days == interval:
|
||||
return cert
|
||||
|
||||
|
||||
def create(label, plugin_name, options, description, certificates):
|
||||
"""
|
||||
Creates a new destination, that can then be used as a destination for certificates.
|
||||
|
||||
:param label: Notification common name
|
||||
:param plugin_name:
|
||||
:param options:
|
||||
:param description:
|
||||
:rtype : Notification
|
||||
:return:
|
||||
"""
|
||||
notification = Notification(label=label, options=options, plugin_name=plugin_name, description=description)
|
||||
notification = database.update_list(notification, 'certificates', Certificate, certificates)
|
||||
return database.create(notification)
|
||||
|
||||
|
||||
def update(notification_id, label, options, description, certificates):
|
||||
"""
|
||||
Updates an existing destination.
|
||||
|
||||
:param label: Notification common name
|
||||
:param options:
|
||||
:param description:
|
||||
:rtype : Notification
|
||||
:return:
|
||||
"""
|
||||
notification = get(notification_id)
|
||||
|
||||
notification.label = label
|
||||
notification.options = options
|
||||
notification.description = description
|
||||
notification = database.update_list(notification, 'certificates', Certificate, certificates)
|
||||
|
||||
return database.update(notification)
|
||||
|
||||
|
||||
def delete(notification_id):
|
||||
"""
|
||||
Deletes an notification.
|
||||
|
||||
:param notification_id: Lemur assigned ID
|
||||
"""
|
||||
database.delete(get(notification_id))
|
||||
|
||||
|
||||
def get(notification_id):
|
||||
"""
|
||||
Retrieves an notification by it's lemur assigned ID.
|
||||
|
||||
:param notification_id: Lemur assigned ID
|
||||
:rtype : Notification
|
||||
:return:
|
||||
"""
|
||||
return database.get(Notification, notification_id)
|
||||
|
||||
|
||||
def get_by_label(label):
|
||||
"""
|
||||
Retrieves a notification by it's label
|
||||
|
||||
:param label:
|
||||
:return:
|
||||
"""
|
||||
return database.get(Notification, label, field='label')
|
||||
|
||||
|
||||
def get_all():
|
||||
"""
|
||||
Retrieves all notification currently known by Lemur.
|
||||
|
||||
:return:
|
||||
"""
|
||||
query = database.session_query(Notification)
|
||||
return database.find_all(query, Notification, {}).all()
|
||||
|
||||
|
||||
def render(args):
|
||||
sort_by = args.pop('sort_by')
|
||||
sort_dir = args.pop('sort_dir')
|
||||
page = args.pop('page')
|
||||
count = args.pop('count')
|
||||
filt = args.pop('filter')
|
||||
certificate_id = args.pop('certificate_id', None)
|
||||
|
||||
if certificate_id:
|
||||
query = database.session_query(Notification).join(Certificate, Notification.certificate)
|
||||
query = query.filter(Certificate.id == certificate_id)
|
||||
else:
|
||||
query = database.session_query(Notification)
|
||||
|
||||
if filt:
|
||||
terms = filt.split(';')
|
||||
if terms[0] == 'active' and terms[1] == 'false':
|
||||
query = query.filter(Notification.active == False) # noqa
|
||||
elif terms[0] == 'active' and terms[1] == 'true':
|
||||
query = query.filter(Notification.active == True) # noqa
|
||||
else:
|
||||
query = database.filter(query, Notification, terms)
|
||||
|
||||
query = database.find_all(query, Notification, args)
|
||||
|
||||
if sort_by and sort_dir:
|
||||
query = database.sort(query, Notification, sort_by, sort_dir)
|
||||
|
||||
return database.paginate(query, page, count)
|
455
lemur/notifications/views.py
Normal file
455
lemur/notifications/views.py
Normal file
@ -0,0 +1,455 @@
|
||||
"""
|
||||
.. module: lemur.notifications.views
|
||||
:platform: Unix
|
||||
:synopsis: This module contains all of the accounts view code.
|
||||
: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 Blueprint
|
||||
from flask.ext.restful import Api, reqparse, fields
|
||||
from lemur.notifications import service
|
||||
|
||||
from lemur.auth.service import AuthenticatedResource
|
||||
from lemur.common.utils import paginated_parser, marshal_items
|
||||
|
||||
|
||||
mod = Blueprint('notifications', __name__)
|
||||
api = Api(mod)
|
||||
|
||||
|
||||
FIELDS = {
|
||||
'description': fields.String,
|
||||
'notificationOptions': fields.Raw(attribute='options'),
|
||||
'pluginName': fields.String(attribute='plugin_name'),
|
||||
'label': fields.String,
|
||||
'active': fields.Boolean,
|
||||
'id': fields.Integer,
|
||||
}
|
||||
|
||||
|
||||
class NotificationsList(AuthenticatedResource):
|
||||
""" Defines the 'notifications' endpoint """
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(NotificationsList, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self):
|
||||
"""
|
||||
.. http:get:: /notifications
|
||||
|
||||
The current account list
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /notifications HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"description": "An example",
|
||||
"notificationOptions": [
|
||||
{
|
||||
"name": "interval",
|
||||
"required": true,
|
||||
"value": 5,
|
||||
"helpMessage": "Number of days to be alert before expiration.",
|
||||
"validation": "^\\d+$",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"available": [
|
||||
"days",
|
||||
"weeks",
|
||||
"months"
|
||||
],
|
||||
"name": "unit",
|
||||
"required": true,
|
||||
"value": "weeks",
|
||||
"helpMessage": "Interval unit",
|
||||
"validation": "",
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"name": "recipients",
|
||||
"required": true,
|
||||
"value": "kglisson@netflix.com,example@netflix.com",
|
||||
"helpMessage": "Comma delimited list of email addresses",
|
||||
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||
"type": "str"
|
||||
}
|
||||
],
|
||||
"label": "example",
|
||||
"pluginName": "email-notification",
|
||||
"active": true,
|
||||
"id": 2
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
:query filter: key value pair. format is k=v;
|
||||
:query limit: limit number. default is 10
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
parser = paginated_parser.copy()
|
||||
args = parser.parse_args()
|
||||
return service.render(args)
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def post(self):
|
||||
"""
|
||||
.. http:post:: /notifications
|
||||
|
||||
Creates a new account
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /notifications HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"description": "a test",
|
||||
"notificationOptions": [
|
||||
{
|
||||
"name": "interval",
|
||||
"required": true,
|
||||
"value": 5,
|
||||
"helpMessage": "Number of days to be alert before expiration.",
|
||||
"validation": "^\\d+$",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"available": [
|
||||
"days",
|
||||
"weeks",
|
||||
"months"
|
||||
],
|
||||
"name": "unit",
|
||||
"required": true,
|
||||
"value": "weeks",
|
||||
"helpMessage": "Interval unit",
|
||||
"validation": "",
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"name": "recipients",
|
||||
"required": true,
|
||||
"value": "kglisson@netflix.com,example@netflix.com",
|
||||
"helpMessage": "Comma delimited list of email addresses",
|
||||
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||
"type": "str"
|
||||
}
|
||||
],
|
||||
"label": "test",
|
||||
"pluginName": "email-notification",
|
||||
"active": true,
|
||||
"id": 2
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"description": "a test",
|
||||
"notificationOptions": [
|
||||
{
|
||||
"name": "interval",
|
||||
"required": true,
|
||||
"value": 5,
|
||||
"helpMessage": "Number of days to be alert before expiration.",
|
||||
"validation": "^\\d+$",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"available": [
|
||||
"days",
|
||||
"weeks",
|
||||
"months"
|
||||
],
|
||||
"name": "unit",
|
||||
"required": true,
|
||||
"value": "weeks",
|
||||
"helpMessage": "Interval unit",
|
||||
"validation": "",
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"name": "recipients",
|
||||
"required": true,
|
||||
"value": "kglisson@netflix.com,example@netflix.com",
|
||||
"helpMessage": "Comma delimited list of email addresses",
|
||||
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||
"type": "str"
|
||||
}
|
||||
],
|
||||
"label": "test",
|
||||
"pluginName": "email-notification",
|
||||
"active": true,
|
||||
"id": 2
|
||||
}
|
||||
|
||||
:arg accountNumber: aws account number
|
||||
:arg label: human readable account label
|
||||
:arg comments: some description about the account
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
self.reqparse.add_argument('label', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
|
||||
self.reqparse.add_argument('description', type=str, location='json')
|
||||
self.reqparse.add_argument('certificates', type=list, default=[], location='json')
|
||||
|
||||
args = self.reqparse.parse_args()
|
||||
return service.create(
|
||||
args['label'],
|
||||
args['plugin']['slug'],
|
||||
args['plugin']['pluginOptions'],
|
||||
args['description'],
|
||||
args['certificates']
|
||||
)
|
||||
|
||||
|
||||
class Notifications(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(Notifications, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, notification_id):
|
||||
"""
|
||||
.. http:get:: /notifications/1
|
||||
|
||||
Get a specific account
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /notifications/1 HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"description": "a test",
|
||||
"notificationOptions": [
|
||||
{
|
||||
"name": "interval",
|
||||
"required": true,
|
||||
"value": 5,
|
||||
"helpMessage": "Number of days to be alert before expiration.",
|
||||
"validation": "^\\d+$",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"available": [
|
||||
"days",
|
||||
"weeks",
|
||||
"months"
|
||||
],
|
||||
"name": "unit",
|
||||
"required": true,
|
||||
"value": "weeks",
|
||||
"helpMessage": "Interval unit",
|
||||
"validation": "",
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"name": "recipients",
|
||||
"required": true,
|
||||
"value": "kglisson@netflix.com,example@netflix.com",
|
||||
"helpMessage": "Comma delimited list of email addresses",
|
||||
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||
"type": "str"
|
||||
}
|
||||
],
|
||||
"label": "test",
|
||||
"pluginName": "email-notification",
|
||||
"active": true,
|
||||
"id": 2
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return service.get(notification_id)
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def put(self, notification_id):
|
||||
"""
|
||||
.. http:put:: /notifications/1
|
||||
|
||||
Updates an account
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /notifications/1 HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "labelChanged",
|
||||
"comments": "this is a thing"
|
||||
}
|
||||
|
||||
:arg accountNumber: aws account number
|
||||
:arg label: human readable account label
|
||||
:arg comments: some description about the account
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
self.reqparse.add_argument('label', type=str, location='json', required=True)
|
||||
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
|
||||
self.reqparse.add_argument('certificates', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('description', type=str, location='json')
|
||||
|
||||
args = self.reqparse.parse_args()
|
||||
return service.update(
|
||||
notification_id,
|
||||
args['label'],
|
||||
args['plugin']['pluginOptions'],
|
||||
args['description'],
|
||||
args['certificates']
|
||||
)
|
||||
|
||||
def delete(self, notification_id):
|
||||
service.delete(notification_id)
|
||||
return {'result': True}
|
||||
|
||||
|
||||
class CertificateNotifications(AuthenticatedResource):
|
||||
""" Defines the 'certificate/<int:certificate_id/notifications'' endpoint """
|
||||
def __init__(self):
|
||||
super(CertificateNotifications, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, certificate_id):
|
||||
"""
|
||||
.. http:get:: /certificates/1/notifications
|
||||
|
||||
The current account list for a given certificates
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /certificates/1/notifications HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"description": "An example",
|
||||
"notificationOptions": [
|
||||
{
|
||||
"name": "interval",
|
||||
"required": true,
|
||||
"value": 555,
|
||||
"helpMessage": "Number of days to be alert before expiration.",
|
||||
"validation": "^\\d+$",
|
||||
"type": "int"
|
||||
},
|
||||
{
|
||||
"available": [
|
||||
"days",
|
||||
"weeks",
|
||||
"months"
|
||||
],
|
||||
"name": "unit",
|
||||
"required": true,
|
||||
"value": "weeks",
|
||||
"helpMessage": "Interval unit",
|
||||
"validation": "",
|
||||
"type": "select"
|
||||
},
|
||||
{
|
||||
"name": "recipients",
|
||||
"required": true,
|
||||
"value": "kglisson@netflix.com,example@netflix.com",
|
||||
"helpMessage": "Comma delimited list of email addresses",
|
||||
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||
"type": "str"
|
||||
}
|
||||
],
|
||||
"label": "example",
|
||||
"pluginName": "email-notification",
|
||||
"active": true,
|
||||
"id": 2
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
:query filter: key value pair. format is k=v;
|
||||
:query limit: limit number. default is 10
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
parser = paginated_parser.copy()
|
||||
args = parser.parse_args()
|
||||
args['certificate_id'] = certificate_id
|
||||
return service.render(args)
|
||||
|
||||
|
||||
api.add_resource(NotificationsList, '/notifications', endpoint='notifications')
|
||||
api.add_resource(Notifications, '/notifications/<int:notification_id>', endpoint='notification')
|
||||
api.add_resource(CertificateNotifications, '/certificates/<int:certificate_id>/notifications',
|
||||
endpoint='certificateNotifications')
|
@ -1,3 +1,4 @@
|
||||
from .destination import DestinationPlugin # noqa
|
||||
from .issuer import IssuerPlugin # noqa
|
||||
from .source import SourcePlugin # noqa
|
||||
from .notification import NotificationPlugin, ExpirationNotificationPlugin # noqa
|
||||
|
52
lemur/plugins/bases/notification.py
Normal file
52
lemur/plugins/bases/notification.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
.. module: lemur.bases.notification
|
||||
: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 lemur.plugins.base import Plugin
|
||||
|
||||
|
||||
class NotificationPlugin(Plugin):
|
||||
"""
|
||||
This is the base class from which all of the supported
|
||||
issuers will inherit from.
|
||||
"""
|
||||
type = 'notification'
|
||||
|
||||
def send(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ExpirationNotificationPlugin(NotificationPlugin):
|
||||
"""
|
||||
This is the base class for all expiration notification plugins.
|
||||
It contains some default options that are needed for all expiration
|
||||
notification plugins.
|
||||
"""
|
||||
default_options = [
|
||||
{
|
||||
'name': 'interval',
|
||||
'type': 'int',
|
||||
'required': True,
|
||||
'validation': '^\d+$',
|
||||
'helpMessage': 'Number of days to be alert before expiration.',
|
||||
},
|
||||
{
|
||||
'name': 'unit',
|
||||
'type': 'select',
|
||||
'required': True,
|
||||
'validation': '',
|
||||
'available': ['days', 'weeks', 'months'],
|
||||
'helpMessage': 'Interval unit',
|
||||
}
|
||||
]
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return list(self.default_options) + self.additional_options
|
||||
|
||||
def send(self):
|
||||
raise NotImplementedError
|
@ -61,7 +61,7 @@ class AWSSourcePlugin(SourcePlugin):
|
||||
options = [
|
||||
{
|
||||
'name': 'accountNumber',
|
||||
'type': 'int',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '/^[0-9]{12,12}$/',
|
||||
'helpMessage': 'Must be a valid AWS account number!',
|
||||
|
5
lemur/plugins/lemur_email/__init__.py
Normal file
5
lemur/plugins/lemur_email/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
try:
|
||||
VERSION = __import__('pkg_resources') \
|
||||
.get_distribution(__name__).version
|
||||
except Exception, e:
|
||||
VERSION = 'unknown'
|
76
lemur/plugins/lemur_email/plugin.py
Normal file
76
lemur/plugins/lemur_email/plugin.py
Normal file
@ -0,0 +1,76 @@
|
||||
"""
|
||||
.. module: lemur.plugins.lemur_aws.aws
|
||||
: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>
|
||||
"""
|
||||
import boto.ses
|
||||
from flask import current_app
|
||||
from flask_mail import Message
|
||||
|
||||
from lemur.extensions import smtp_mail
|
||||
|
||||
from lemur.plugins.bases import ExpirationNotificationPlugin
|
||||
from lemur.plugins import lemur_email as email
|
||||
|
||||
|
||||
from lemur.plugins.lemur_email.templates.config import env
|
||||
|
||||
|
||||
def find_value(name, options):
|
||||
for o in options:
|
||||
if o.get(name):
|
||||
return o['value']
|
||||
|
||||
|
||||
class EmailNotificationPlugin(ExpirationNotificationPlugin):
|
||||
title = 'Email'
|
||||
slug = 'email-notification'
|
||||
description = 'Sends expiration email notifications'
|
||||
version = email.VERSION
|
||||
|
||||
author = 'Kevin Glisson'
|
||||
author_url = 'https://github.com/netflix/lemur'
|
||||
|
||||
additional_options = [
|
||||
{
|
||||
'name': 'recipients',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '^([\w+-.%]+@[\w-.]+\.[A-Za-z]{2,4},?)+$',
|
||||
'helpMessage': 'Comma delimited list of email addresses',
|
||||
},
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def send(event_type, message, targets, options, **kwargs):
|
||||
"""
|
||||
Configures all Lemur email messaging
|
||||
|
||||
:param event_type:
|
||||
:param options:
|
||||
"""
|
||||
subject = 'Notification: Lemur'
|
||||
|
||||
if event_type == 'expiration':
|
||||
subject = 'Notification: SSL Certificate Expiration '
|
||||
|
||||
# jinja template depending on type
|
||||
template = env.get_template('{}.html'.format(event_type))
|
||||
body = template.render(**kwargs)
|
||||
|
||||
s_type = current_app.config.get("LEMUR_EMAIL_SENDER").lower()
|
||||
if s_type == 'ses':
|
||||
conn = boto.connect_ses()
|
||||
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, targets, format='html')
|
||||
|
||||
elif s_type == 'smtp':
|
||||
msg = Message(subject, recipients=targets)
|
||||
msg.body = "" # kinda a weird api for sending html emails
|
||||
msg.html = body
|
||||
smtp_mail.send(msg)
|
||||
|
||||
else:
|
||||
current_app.logger.error("No mail carrier specified, notification emails were not able to be sent!")
|
0
lemur/plugins/lemur_email/templates/__init__.py
Normal file
0
lemur/plugins/lemur_email/templates/__init__.py
Normal file
@ -72,6 +72,12 @@
|
||||
<tr>
|
||||
<td>{{ message.owner }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Creator</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ message.creator }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Not Before</strong></td>
|
||||
</tr>
|
@ -65,13 +65,13 @@ class PluginsList(AuthenticatedResource):
|
||||
"id": 2,
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"comments": "this is a thing"
|
||||
"description": "this is a thing"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
"description": "this is a thing"
|
||||
},
|
||||
]
|
||||
"total": 2
|
||||
@ -80,19 +80,24 @@ class PluginsList(AuthenticatedResource):
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
self.reqparse.add_argument('type', type=str, location='args')
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
if args['type']:
|
||||
return list(plugins.all(plugin_type=args['type']))
|
||||
|
||||
return plugins.all()
|
||||
|
||||
|
||||
class PluginsTypeList(AuthenticatedResource):
|
||||
""" Defines the 'plugins' endpoint """
|
||||
class Plugins(AuthenticatedResource):
|
||||
""" Defines the the 'plugins' endpoint """
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(PluginsTypeList, self).__init__()
|
||||
super(Plugins, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, plugin_type):
|
||||
def get(self, name):
|
||||
"""
|
||||
.. http:get:: /plugins/issuer
|
||||
.. http:get:: /plugins/<name>
|
||||
|
||||
The current plugin list
|
||||
|
||||
@ -100,7 +105,7 @@ class PluginsTypeList(AuthenticatedResource):
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /plugins/issuer HTTP/1.1
|
||||
GET /plugins HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
@ -113,27 +118,16 @@ class PluginsTypeList(AuthenticatedResource):
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
]
|
||||
"total": 2
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"description": "this is a thing"
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
"""
|
||||
return list(plugins.all(plugin_type=plugin_type))
|
||||
return plugins.get(name)
|
||||
|
||||
|
||||
api.add_resource(PluginsList, '/plugins', endpoint='plugins')
|
||||
api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType')
|
||||
api.add_resource(Plugins, '/plugins/<name>', endpoint='pluginName')
|
||||
|
@ -2,15 +2,29 @@
|
||||
|
||||
angular.module('lemur')
|
||||
|
||||
.controller('AuthorityEditController', function ($scope, $routeParams, AuthorityApi, AuthorityService, RoleService){
|
||||
AuthorityApi.get($routeParams.id).then(function (authority) {
|
||||
.controller('AuthorityEditController', function ($scope, $modalInstance, AuthorityApi, AuthorityService, RoleService, editId){
|
||||
AuthorityApi.get(editId).then(function (authority) {
|
||||
AuthorityService.getRoles(authority);
|
||||
$scope.authority = authority;
|
||||
});
|
||||
|
||||
$scope.authorityService = AuthorityService;
|
||||
$scope.save = AuthorityService.update;
|
||||
$scope.roleService = RoleService;
|
||||
|
||||
$scope.save = function (authority) {
|
||||
AuthorityService.update(authority).then(
|
||||
function () {
|
||||
$modalInstance.close();
|
||||
},
|
||||
function () {
|
||||
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
})
|
||||
|
||||
.controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) {
|
||||
@ -25,7 +39,7 @@ angular.module('lemur')
|
||||
});
|
||||
};
|
||||
|
||||
PluginService.get('issuer').then(function (plugins) {
|
||||
PluginService.getByType('issuer').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
});
|
||||
|
||||
|
@ -1,44 +1,41 @@
|
||||
<h2 class="featurette-heading">Edit</span> Authority <span class="text-muted"><small>Chain of command
|
||||
</small></span></h2>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<a href="#/authorities" class="btn btn-danger pull-right">Cancel</a>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2">
|
||||
Roles
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" ng-model="authority.selectedRole" placeholder="Role Name"
|
||||
typeahead="role.name for role in roleService.findRoleByName($viewValue)" typeahead-loading="loadingRoles"
|
||||
class="form-control input-md" typeahead-on-select="authority.attachRole($item)" typeahead-min-wait="50"
|
||||
tooltip="Roles control which authorities a user can issue certificates from"
|
||||
tooltip-trigger="focus" tooltip-placement="top">
|
||||
<span class="input-group-btn">
|
||||
<button ng-model="roles.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||
<span class="badge">{{ authority.roles.length || 0 }}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<table ng-show="authority.roles" class="table">
|
||||
<tr ng-repeat="role in authority.roles track by $index">
|
||||
<td><a class="btn btn-sm btn-info" href="#/roles/{{ role.id }}/edit">{{ role.name }}</a></td>
|
||||
<td><span class="text-muted">{{ role.description }}</span></td>
|
||||
<td>
|
||||
<button type="button" ng-click="authority.removeRole($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button ng-click="save(authority)" class="btn btn-success pull-right">Save</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">
|
||||
<div class="modal-header">Edit Authority <span class="text-muted"><small>chain of command!</small></span></div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2">
|
||||
Roles
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" ng-model="authority.selectedRole" placeholder="Role Name"
|
||||
typeahead="role.name for role in roleService.findRoleByName($viewValue)" typeahead-loading="loadingRoles"
|
||||
class="form-control input-md" typeahead-on-select="authority.attachRole($item)" typeahead-min-wait="50"
|
||||
tooltip="Roles control which authorities a user can issue certificates from"
|
||||
tooltip-trigger="focus" tooltip-placement="top">
|
||||
<span class="input-group-btn">
|
||||
<button ng-model="roles.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||
<span class="badge">{{ authority.roles.length || 0 }}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<table ng-show="authority.roles" class="table">
|
||||
<tr ng-repeat="role in authority.roles track by $index">
|
||||
<td><a class="btn btn-sm btn-info" href="#/roles/{{ role.id }}/edit">{{ role.name }}</a></td>
|
||||
<td><span class="text-muted">{{ role.description }}</span></td>
|
||||
<td>
|
||||
<button type="button" ng-click="authority.removeRole($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-click="save(authority)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
|
||||
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
19
lemur/static/app/angular/authorities/services.js
vendored
19
lemur/static/app/angular/authorities/services.js
vendored
@ -65,6 +65,19 @@ angular.module('lemur')
|
||||
});
|
||||
};
|
||||
|
||||
AuthorityService.findActiveAuthorityByName = function (filterValue) {
|
||||
return AuthorityApi.getList({'filter[name]': filterValue})
|
||||
.then(function (authorities) {
|
||||
var activeAuthorities = [];
|
||||
_.each(authorities, function (authority) {
|
||||
if (authority.active) {
|
||||
activeAuthorities.push(authority);
|
||||
}
|
||||
});
|
||||
return activeAuthorities;
|
||||
});
|
||||
};
|
||||
|
||||
AuthorityService.create = function (authority) {
|
||||
authority.attachSubAltName();
|
||||
return AuthorityApi.post(authority).then(
|
||||
@ -86,7 +99,7 @@ angular.module('lemur')
|
||||
};
|
||||
|
||||
AuthorityService.update = function (authority) {
|
||||
authority.put().then(
|
||||
return authority.put().then(
|
||||
function () {
|
||||
toaster.pop({
|
||||
type: 'success',
|
||||
@ -105,13 +118,13 @@ angular.module('lemur')
|
||||
};
|
||||
|
||||
AuthorityService.getRoles = function (authority) {
|
||||
authority.getList('roles').then(function (roles) {
|
||||
return authority.getList('roles').then(function (roles) {
|
||||
authority.roles = roles;
|
||||
});
|
||||
};
|
||||
|
||||
AuthorityService.updateActive = function (authority) {
|
||||
authority.put().then(
|
||||
return authority.put().then(
|
||||
function () {
|
||||
toaster.pop({
|
||||
type: 'success',
|
||||
|
@ -46,7 +46,7 @@ angular.module('lemur')
|
||||
$scope.edit = function (authorityId) {
|
||||
var modalInstance = $modal.open({
|
||||
animation: true,
|
||||
templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html',
|
||||
templateUrl: '/angular/authorities/authority/authorityEdit.tpl.html',
|
||||
controller: 'AuthorityEditController',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
|
@ -1,17 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('lemur')
|
||||
.controller('CertificateEditController', function ($scope, $routeParams, CertificateApi, CertificateService, MomentService) {
|
||||
CertificateApi.get($routeParams.id).then(function (certificate) {
|
||||
.controller('CertificateEditController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, NotificationService, editId) {
|
||||
CertificateApi.get(editId).then(function (certificate) {
|
||||
CertificateService.getNotifications(certificate);
|
||||
CertificateService.getDestinations(certificate);
|
||||
$scope.certificate = certificate;
|
||||
});
|
||||
|
||||
$scope.momentService = MomentService;
|
||||
$scope.save = CertificateService.update;
|
||||
$scope.cancel = function () {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
$scope.save = function (certificate) {
|
||||
CertificateService.update(certificate).then(function () {
|
||||
$modalInstance.close();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
})
|
||||
|
||||
.controller('CertificateCreateController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, ELBService, AuthorityService, PluginService, MomentService, WizardHandler, LemurRestangular) {
|
||||
.controller('CertificateCreateController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, ELBService, AuthorityService, PluginService, MomentService, WizardHandler, LemurRestangular, NotificationService) {
|
||||
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
|
||||
|
||||
$scope.create = function (certificate) {
|
||||
@ -77,11 +88,12 @@ angular.module('lemur')
|
||||
|
||||
};
|
||||
|
||||
PluginService.get('destination').then(function (plugins) {
|
||||
PluginService.getByType('destination').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
});
|
||||
|
||||
$scope.elbService = ELBService;
|
||||
$scope.authorityService = AuthorityService;
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
});
|
||||
|
@ -7,14 +7,11 @@
|
||||
<wz-step title="Tracking" canexit="trackingForm.$valid">
|
||||
<ng-include src="'angular/certificates/certificate/tracking.tpl.html'"></ng-include>
|
||||
</wz-step>
|
||||
<wz-step title="Options" canenter="enterValidation">
|
||||
<ng-include src="'angular/certificates/certificate/options.tpl.html'"></ng-include>
|
||||
</wz-step>
|
||||
<wz-step title="Distinguished Name" canenter="exitTracking" canexit="exitDN">
|
||||
<ng-include src="'angular/certificates/certificate/distinguishedName.tpl.html'"></ng-include>
|
||||
</wz-step>
|
||||
<wz-step title="Destinations" canenter="enterValidation">
|
||||
<ng-include src="'angular/certificates/certificate/destinations.tpl.html'"></ng-include>
|
||||
<wz-step title="Options" canenter="enterValidation">
|
||||
<ng-include src="'angular/certificates/certificate/options.tpl.html'"></ng-include>
|
||||
</wz-step>
|
||||
</wizard>
|
||||
</div>
|
||||
|
@ -3,26 +3,26 @@
|
||||
Destinations
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" ng-model="certificate.selectedDestination" placeholder="AWS, TheSecret..."
|
||||
typeahead="destination.label for destination in destinationService.findDestinationsByName($viewValue)" typeahead-loading="loadingDestinations"
|
||||
class="form-control input-md" typeahead-on-select="certificate.attachDestination($item)" typeahead-min-wait="50"
|
||||
tooltip="Lemur can upload certificates to any pre-defined destination"
|
||||
tooltip-trigger="focus" tooltip-placement="top">
|
||||
<div class="input-group">
|
||||
<input type="text" ng-model="certificate.selectedDestination" placeholder="AWS, TheSecret..."
|
||||
typeahead="destination.label for destination in destinationService.findDestinationsByName($viewValue)" typeahead-loading="loadingDestinations"
|
||||
class="form-control input-md" typeahead-on-select="certificate.attachDestination($item)" typeahead-min-wait="50"
|
||||
tooltip="Lemur can upload certificates to any pre-defined destination"
|
||||
tooltip-trigger="focus" tooltip-placement="top">
|
||||
<span class="input-group-btn">
|
||||
<button ng-model="destinations.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||
<span class="badge">{{ certificate.destinations.length || 0 }}</span>
|
||||
<span class="badge">{{ certificate.destinations.length || 0 }}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tr ng-repeat="destination in certificate.destinations track by $index">
|
||||
<td><a class="btn btn-sm btn-info" href="#/destinations/{{ destination.id }}/certificates">{{ destination.label }}</a></td>
|
||||
<td><span class="text-muted">{{ destination.description }}</span></td>
|
||||
<td>
|
||||
<button type="button" ng-click="certificate.removeDestination($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tr ng-repeat="destination in certificate.destinations track by $index">
|
||||
<td><a class="btn btn-sm btn-info" href="#/destinations/{{ destination.id }}/certificates">{{ destination.label }}</a></td>
|
||||
<td><span class="text-muted">{{ destination.description }}</span></td>
|
||||
<td>
|
||||
<button type="button" ng-click="certificate.removeDestination($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,83 +1,84 @@
|
||||
<form name="trackingForm" novalidate>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.ownerEmail.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.ownerEmail.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Owner
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="email" name="ownerEmail" ng-model="certificate.owner" placeholder="TeamDL@netflix.com" tooltip="This is the certificates team distribution list or main point of contact" class="form-control" required/>
|
||||
<p ng-show="trackingForm.ownerEmail.$invalid && !trackingForm.ownerEmail.$pristine" class="help-block">You must enter an Certificate owner</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.description.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.description.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Description
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea name="description" ng-model="certificate.description" placeholder="Something elegant" class="form-control" ng-pattern="/^[\w\-\s]+$/" required></textarea>
|
||||
<p ng-show="trackingForm.description.$invalid && !trackingForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.selectedAuthority.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.selectedAuthority.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Certificate Authority
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group col-sm-12">
|
||||
<input name="selectedAuthority" tooltip="If you are unsure which authority you need; you most likely want to use 'verisign'" type="text" ng-model="certificate.selectedAuthority" placeholder="Authority Name" typeahead-on-select="certificate.attachAuthority($item)"
|
||||
typeahead="authority.name for authority in authorityService.findAuthorityByName($viewValue)" typeahead-loading="loadingAuthorities"
|
||||
class="form-control" typeahead-wait-ms="100" typeahead-template-url="angular/authorities/authority/select.tpl.html" required>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.ownerEmail.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.ownerEmail.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Owner
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="email" name="ownerEmail" ng-model="certificate.owner" placeholder="TeamDL@netflix.com" tooltip="This is the certificates team distribution list or main point of contact" class="form-control" required/>
|
||||
<p ng-show="trackingForm.ownerEmail.$invalid && !trackingForm.ownerEmail.$pristine" class="help-block">You must enter an Certificate owner</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="certificate.authority" class="form-group">
|
||||
<label class="control-label col-sm-2">
|
||||
Certificate Template
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" ng-change="certificate.useTemplate()" name="certificateTemplate" ng-model="certificate.template" ng-options="template.name for template in templates"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.commonName.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.commonName.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Common Name
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input name="commonName" tooltip="If you need a certificate with multiple domains enter your primary domain here and the rest under 'Subject Alternate Names' in the next panel" ng-model="certificate.commonName" placeholder="Common Name" class="form-control" required/>
|
||||
<p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" tooltip="If no date is selected Lemur attempts to issue a 2 year certificate">
|
||||
Validity Range <span class="glyphicon glyphicon-question-sign"></span>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<div>
|
||||
<div class="input-group">
|
||||
<input tooltip="Starting Date (yyyy/MM/dd)" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="$parent.openNotBefore.isOpen" min-date="certificate.authority.notBefore" max-date="certificate.authority.maxDate" ng-model="certificate.validityStart" />
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.description.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.description.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Description
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea name="description" ng-model="certificate.description" placeholder="Something elegant" class="form-control" ng-pattern="/^[\w\-\s]+$/" required></textarea>
|
||||
<p ng-show="trackingForm.description.$invalid && !trackingForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.selectedAuthority.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.selectedAuthority.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Certificate Authority
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group col-sm-12">
|
||||
<input name="selectedAuthority" tooltip="If you are unsure which authority you need; you most likely want to use 'verisign'" type="text" ng-model="certificate.selectedAuthority" placeholder="Authority Name" typeahead-on-select="certificate.attachAuthority($item)"
|
||||
typeahead="authority.name for authority in authorityService.findActiveAuthorityByName($viewValue)" typeahead-loading="loadingAuthorities"
|
||||
class="form-control" typeahead-wait-ms="100" typeahead-template-url="angular/authorities/authority/select.tpl.html" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="certificate.authority" class="form-group">
|
||||
<label class="control-label col-sm-2">
|
||||
Certificate Template
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" ng-change="certificate.useTemplate()" name="certificateTemplate" ng-model="certificate.template" ng-options="template.name for template in templates"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': trackingForm.commonName.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.commonName.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
Common Name
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input name="commonName" tooltip="If you need a certificate with multiple domains enter your primary domain here and the rest under 'Subject Alternate Names' in the next panel" ng-model="certificate.commonName" placeholder="Common Name" class="form-control" required/>
|
||||
<p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" tooltip="If no date is selected Lemur attempts to issue a 2 year certificate">
|
||||
Validity Range <span class="glyphicon glyphicon-question-sign"></span>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<div>
|
||||
<div class="input-group">
|
||||
<input tooltip="Starting Date (yyyy/MM/dd)" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="$parent.openNotBefore.isOpen" min-date="certificate.authority.notBefore" max-date="certificate.authority.maxDate" ng-model="certificate.validityStart" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" ng-click="openNotBefore($event)"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span style="padding-top: 15px" class="text-center col-sm-2"><label><span class="glyphicon glyphicon-resize-horizontal"></span></label></span>
|
||||
<div class="col-sm-4">
|
||||
<div>
|
||||
<div class="input-group">
|
||||
<input tooltip="Ending Date (yyyy/MM/dd)" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="$parent.openNotAfter.isOpen" min-date="certificate.authority.notBefore" max-date="certificate.authority.maxDate" ng-model="certificate.validityEnd" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span style="padding-top: 15px" class="text-center col-sm-2"><label><span class="glyphicon glyphicon-resize-horizontal"></span></label></span>
|
||||
<div class="col-sm-4">
|
||||
<div>
|
||||
<div class="input-group">
|
||||
<input tooltip="Ending Date (yyyy/MM/dd)" class="form-control" datepicker-popup="yyyy/MM/dd" is-open="$parent.openNotAfter.isOpen" min-date="certificate.authority.notBefore" max-date="certificate.authority.maxDate" ng-model="certificate.validityEnd" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" ng-click="openNotAfter($event)"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
@ -2,14 +2,15 @@
|
||||
|
||||
angular.module('lemur')
|
||||
|
||||
.controller('CertificateUploadController', function ($scope, $modalInstance, CertificateService, LemurRestangular, DestinationService, ELBService, PluginService) {
|
||||
.controller('CertificateUploadController', function ($scope, $modalInstance, CertificateService, LemurRestangular, DestinationService, NotificationService, ELBService, PluginService) {
|
||||
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
|
||||
$scope.upload = CertificateService.upload;
|
||||
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
$scope.elbService = ELBService;
|
||||
|
||||
PluginService.get('destination').then(function (plugins) {
|
||||
PluginService.getByType('destination').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
});
|
||||
|
||||
|
@ -61,6 +61,7 @@
|
||||
class="help-block">Enter a valid certificate.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -67,6 +67,16 @@ angular.module('lemur')
|
||||
removeDestination: function (index) {
|
||||
this.destinations.splice(index, 1);
|
||||
},
|
||||
attachNotification: function (notification) {
|
||||
this.selectedNotification = null;
|
||||
if (this.notifications === undefined) {
|
||||
this.notifications = [];
|
||||
}
|
||||
this.notifications.push(notification);
|
||||
},
|
||||
removeNotification: function (index) {
|
||||
this.notifications.splice(index, 1);
|
||||
},
|
||||
attachELB: function (elb) {
|
||||
this.selectedELB = null;
|
||||
if (this.elbs === undefined) {
|
||||
@ -89,7 +99,7 @@ angular.module('lemur')
|
||||
});
|
||||
return LemurRestangular.all('certificates');
|
||||
})
|
||||
.service('CertificateService', function ($location, CertificateApi, toaster) {
|
||||
.service('CertificateService', function ($location, CertificateApi, LemurRestangular, toaster) {
|
||||
var CertificateService = this;
|
||||
CertificateService.findCertificatesByName = function (filterValue) {
|
||||
return CertificateApi.getList({'filter[name]': filterValue})
|
||||
@ -120,7 +130,7 @@ angular.module('lemur')
|
||||
};
|
||||
|
||||
CertificateService.update = function (certificate) {
|
||||
certificate.put().then(function () {
|
||||
return LemurRestangular.copy(certificate).put().then(function () {
|
||||
toaster.pop({
|
||||
type: 'success',
|
||||
title: certificate.name,
|
||||
@ -131,7 +141,7 @@ angular.module('lemur')
|
||||
};
|
||||
|
||||
CertificateService.upload = function (certificate) {
|
||||
CertificateApi.customPOST(certificate, 'upload').then(
|
||||
return CertificateApi.customPOST(certificate, 'upload').then(
|
||||
function () {
|
||||
toaster.pop({
|
||||
type: 'success',
|
||||
@ -150,7 +160,7 @@ angular.module('lemur')
|
||||
};
|
||||
|
||||
CertificateService.loadPrivateKey = function (certificate) {
|
||||
certificate.customGET('key').then(
|
||||
return certificate.customGET('key').then(
|
||||
function (response) {
|
||||
if (response.key === null) {
|
||||
toaster.pop({
|
||||
@ -172,43 +182,49 @@ angular.module('lemur')
|
||||
};
|
||||
|
||||
CertificateService.getAuthority = function (certificate) {
|
||||
certificate.customGET('authority').then(function (authority) {
|
||||
return certificate.customGET('authority').then(function (authority) {
|
||||
certificate.authority = authority;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.getCreator = function (certificate) {
|
||||
certificate.customGET('creator').then(function (creator) {
|
||||
return certificate.customGET('creator').then(function (creator) {
|
||||
certificate.creator = creator;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.getDestinations = function (certificate) {
|
||||
certificate.getList('destinations').then(function (destinations) {
|
||||
return certificate.getList('destinations').then(function (destinations) {
|
||||
certificate.destinations = destinations;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.getNotifications = function (certificate) {
|
||||
return certificate.getList('notifications').then(function (notifications) {
|
||||
certificate.notifications = notifications;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.getListeners = function (certificate) {
|
||||
certificate.getList('listeners').then(function (listeners) {
|
||||
return certificate.getList('listeners').then(function (listeners) {
|
||||
certificate.listeners = listeners;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.getELBs = function (certificate) {
|
||||
certificate.getList('listeners').then(function (elbs) {
|
||||
return certificate.getList('listeners').then(function (elbs) {
|
||||
certificate.elbs = elbs;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.getDomains = function (certificate) {
|
||||
certificate.getList('domains').then(function (domains) {
|
||||
return certificate.getList('domains').then(function (domains) {
|
||||
certificate.domains = domains;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.updateActive = function (certificate) {
|
||||
certificate.put().then(
|
||||
return certificate.put().then(
|
||||
function () {
|
||||
toaster.pop({
|
||||
type: 'success',
|
||||
|
@ -27,7 +27,7 @@ angular.module('lemur')
|
||||
_.each(data, function (certificate) {
|
||||
CertificateService.getDomains(certificate);
|
||||
CertificateService.getDestinations(certificate);
|
||||
CertificateService.getListeners(certificate);
|
||||
CertificateService.getNotifications(certificate);
|
||||
CertificateService.getAuthority(certificate);
|
||||
CertificateService.getCreator(certificate);
|
||||
});
|
||||
@ -74,6 +74,24 @@ angular.module('lemur')
|
||||
});
|
||||
};
|
||||
|
||||
$scope.edit = function (certificateId) {
|
||||
var modalInstance = $modal.open({
|
||||
animation: true,
|
||||
controller: 'CertificateEditController',
|
||||
templateUrl: '/angular/certificates/certificate/edit.tpl.html',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
editId: function () {
|
||||
return certificateId;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modalInstance.result.then(function () {
|
||||
$scope.certificateTable.reload();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.import = function () {
|
||||
var modalInstance = $modal.open({
|
||||
animation: true,
|
||||
|
@ -30,11 +30,6 @@
|
||||
<li><span class="text-muted">{{ certificate.owner }}</span></li>
|
||||
</ul>
|
||||
</td>
|
||||
<td data-title="'Destinations'" filter="{ 'destination': 'select' }" filter-date="getDestinationDropDown()">
|
||||
<div class="btn-group">
|
||||
<a href="#/destinations/{{ destination.id }}/edit" class="btn btn-sm btn-default" ng-repeat="account in certificate.destinations">{{ destination.label }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getCertificateStatus()">
|
||||
<form>
|
||||
<switch ng-change="certificateService.updateActive(certificate)" id="status" name="status" ng-model="certificate.active" class="green small"></switch>
|
||||
@ -47,70 +42,80 @@
|
||||
{{ certificate.cn }}
|
||||
</td>
|
||||
<td data-title="''">
|
||||
<div class="btn-group-vertical pull-right">
|
||||
<button ng-model="certificate.toggle" class="btn btn-sm btn-info" btn-checkbox btn-checkbox-true="1" butn-checkbox-false="0">View</button>
|
||||
<div class="btn-group pull-right">
|
||||
<button ng-model="certificate.toggle" class="btn btn-sm btn-info" btn-checkbox btn-checkbox-true="1" butn-checkbox-false="0">More</button>
|
||||
<button class="btn btn-sm btn-warning" ng-click="edit(certificate.id)">Edit</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="warning" ng-show="certificate.toggle" ng-repeat-end>
|
||||
<td colspan="5">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<strong>Creator</strong>
|
||||
<span class="pull-right">
|
||||
{{ certificate.creator.email }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Not Before</strong>
|
||||
<span class="pull-right" tooltip="{{ certificate.notBefore }}">
|
||||
{{ momentService.createMoment(certificate.notBefore) }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Not After</strong>
|
||||
<span class="pull-right" tooltip="{{ certificate.notAfter }}">
|
||||
{{ momentService.createMoment(certificate.notAfter) }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>San</strong>
|
||||
<span class="pull-right">
|
||||
<i class="glyphicon glyphicon-ok" ng-show="certificate.san"></i>
|
||||
<i class="glyphicon glyphicon-remove" ng-show="!certificate.san"></i>
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Bits</strong>
|
||||
<span class="pull-right">{{ certificate.bits }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Serial</strong>
|
||||
<span class="pull-right">{{ certificate.serial }}</span>
|
||||
</li>
|
||||
<li tooltip="Lemur will attempt to check a certificates validity, this is used to track whether a certificate as been revoked" class="list-group-item">
|
||||
<strong>Validity</strong>
|
||||
<span class="pull-right">
|
||||
<span ng-show="!certificate.status" class="label label-warning">Unknown</span>
|
||||
<span ng-show="certificate.status == 'revoked'" class="label label-danger">Revoked</span>
|
||||
<span ng-show="certificate.status == 'valid'" class="label label-success">Valid</span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Description</strong>
|
||||
<span class="pull-right">{{ certificate.description }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<h4>Domains</h4>
|
||||
<div class="list-group">
|
||||
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
|
||||
</div>
|
||||
<h4 ng-show="certificate.destinations.total">ARNs</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" ng-repeat="arn in certificate.arns">{{ arn }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<td colspan="6">
|
||||
<tabset justified="true" class="col-md-6">
|
||||
<tab heading="Basic Info">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<strong>Creator</strong>
|
||||
<span class="pull-right">
|
||||
{{ certificate.creator.email }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Not Before</strong>
|
||||
<span class="pull-right" tooltip="{{ certificate.notBefore }}">
|
||||
{{ momentService.createMoment(certificate.notBefore) }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Not After</strong>
|
||||
<span class="pull-right" tooltip="{{ certificate.notAfter }}">
|
||||
{{ momentService.createMoment(certificate.notAfter) }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>San</strong>
|
||||
<span class="pull-right">
|
||||
<i class="glyphicon glyphicon-ok" ng-show="certificate.san"></i>
|
||||
<i class="glyphicon glyphicon-remove" ng-show="!certificate.san"></i>
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Bits</strong>
|
||||
<span class="pull-right">{{ certificate.bits }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Serial</strong>
|
||||
<span class="pull-right">{{ certificate.serial }}</span>
|
||||
</li>
|
||||
<li tooltip="Lemur will attempt to check a certificates validity, this is used to track whether a certificate as been revoked" class="list-group-item">
|
||||
<strong>Validity</strong>
|
||||
<span class="pull-right">
|
||||
<span ng-show="!certificate.status" class="label label-warning">Unknown</span>
|
||||
<span ng-show="certificate.status == 'revoked'" class="label label-danger">Revoked</span>
|
||||
<span ng-show="certificate.status == 'valid'" class="label label-success">Valid</span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Description</strong>
|
||||
<span class="pull-right">{{ certificate.description }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
<tab heading="Notifications">
|
||||
<div class="list-group">
|
||||
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="notification in certificate.notifications">{{ notification.label }}</a>
|
||||
</div>
|
||||
</tab>
|
||||
<tab heading="Destinations">
|
||||
<div class="list-group">
|
||||
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="destination in certificate.destinations">{{ destination.label }}</a>
|
||||
</div>
|
||||
</tab>
|
||||
<tab heading="Domains">
|
||||
<div class="list-group">
|
||||
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
<tabset justified="true" class="col-md-6">
|
||||
<tab heading="Chain">
|
||||
<p>
|
||||
|
@ -5,7 +5,7 @@ angular.module('lemur')
|
||||
.controller('DestinationsCreateController', function ($scope, $modalInstance, PluginService, DestinationService, LemurRestangular){
|
||||
$scope.destination = LemurRestangular.restangularizeElement(null, {}, 'destinations');
|
||||
|
||||
PluginService.get('destination').then(function (plugins) {
|
||||
PluginService.getByType('destination').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
});
|
||||
$scope.save = function (destination) {
|
||||
@ -24,8 +24,14 @@ angular.module('lemur')
|
||||
$scope.destination = destination;
|
||||
});
|
||||
|
||||
PluginService.get('destination').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
PluginService.getByType('destination').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
_.each($scope.plugins, function (plugin) {
|
||||
if (plugin.slug == $scope.destination.pluginName) {
|
||||
plugin.pluginOptions = $scope.destination.destinationOptions;
|
||||
$scope.destination.plugin = plugin;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
$scope.save = function (destination) {
|
||||
|
@ -3,7 +3,7 @@ angular.module('lemur')
|
||||
.service('DestinationApi', function (LemurRestangular) {
|
||||
return LemurRestangular.all('destinations');
|
||||
})
|
||||
.service('DestinationService', function ($location, DestinationApi, toaster) {
|
||||
.service('DestinationService', function ($location, DestinationApi, PluginService, toaster) {
|
||||
var DestinationService = this;
|
||||
DestinationService.findDestinationsByName = function (filterValue) {
|
||||
return DestinationApi.getList({'filter[label]': filterValue})
|
||||
@ -49,5 +49,11 @@ angular.module('lemur')
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
DestinationService.getPlugin = function (destination) {
|
||||
return PluginService.getByName(destination.pluginName).then(function (plugin) {
|
||||
destination.plugin = plugin;
|
||||
});
|
||||
};
|
||||
return DestinationService;
|
||||
});
|
||||
|
@ -23,6 +23,9 @@ angular.module('lemur')
|
||||
getData: function ($defer, params) {
|
||||
DestinationApi.getList(params.url()).then(
|
||||
function (data) {
|
||||
_.each(data, function (destination) {
|
||||
DestinationService.getPlugin(destination);
|
||||
});
|
||||
params.total(data.total);
|
||||
$defer.resolve(data);
|
||||
}
|
||||
|
36
lemur/static/app/angular/plugins/services.js
vendored
36
lemur/static/app/angular/plugins/services.js
vendored
@ -1,14 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('lemur')
|
||||
.service('PluginApi', function (LemurRestangular) {
|
||||
return LemurRestangular.all('plugins');
|
||||
})
|
||||
.service('PluginService', function (PluginApi) {
|
||||
var PluginService = this;
|
||||
PluginService.get = function (type) {
|
||||
return PluginApi.customGETLIST(type).then(function (plugins) {
|
||||
return plugins;
|
||||
});
|
||||
};
|
||||
});
|
||||
.service('PluginApi', function (LemurRestangular) {
|
||||
return LemurRestangular.all('plugins');
|
||||
})
|
||||
.service('PluginService', function (PluginApi) {
|
||||
var PluginService = this;
|
||||
PluginService.get = function () {
|
||||
return PluginApi.getList().then(function (plugins) {
|
||||
return plugins;
|
||||
});
|
||||
};
|
||||
|
||||
PluginService.getByType = function (type) {
|
||||
return PluginApi.getList({'type': type}).then(function (plugins) {
|
||||
return plugins;
|
||||
});
|
||||
};
|
||||
|
||||
PluginService.getByName = function (pluginName) {
|
||||
return PluginApi.customGET(pluginName).then(function (plugin) {
|
||||
return plugin;
|
||||
});
|
||||
};
|
||||
|
||||
return PluginService;
|
||||
});
|
||||
|
@ -55,11 +55,11 @@
|
||||
class="form-control input-md" typeahead-on-select="user.attachRole($item)" typeahead-min-wait="50"
|
||||
tooltip="Roles control which authorities a user can issue certificates from"
|
||||
tooltip-trigger="focus" tooltip-placement="top">
|
||||
<span class="input-group-btn">
|
||||
<button ng-model="roles.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||
<span class="badge">{{ user.roles.total || 0 }}</span>
|
||||
</button>
|
||||
</span>
|
||||
<span class="input-group-btn">
|
||||
<button ng-model="roles.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||
<span class="badge">{{ user.roles.total || 0 }}</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<table ng-show="user.roles" class="table">
|
||||
<tr ng-repeat="role in user.roles track by $index">
|
||||
|
@ -49,13 +49,20 @@
|
||||
</div>
|
||||
<div class="navbar-collapse collapse" ng-controller="LoginController">
|
||||
<ul class="nav navbar-nav navbar-left">
|
||||
<li data-match-route="/dashboard"><a href="/#/dashboard">Dashboard</a></li>
|
||||
<li data-match-route="/certificates"><a href="/#/certificates">Certificates</a></li>
|
||||
<li data-match-route="/authorities"><a href="/#/authorities">Authorities</a></li>
|
||||
<li data-match-route="/domains"><a href="/#/domains">Domains</a></li>
|
||||
<li><a href="/#/roles">Roles</a></li>
|
||||
<li><a href="/#/users">Users</a></li>
|
||||
<li><a href="/#/dashboard">Dashboard</a></li>
|
||||
<li><a href="/#/certificates">Certificates</a></li>
|
||||
<li><a href="/#/authorities">Authorities</a></li>
|
||||
<li><a href="/#/notifications">Notifications</a></li>
|
||||
<li><a href="/#/destinations">Destinations</a></li>
|
||||
<li></li>
|
||||
<li class="dropdown" dropdown on-toggle="toggled(open)">
|
||||
<a href class="dropdown-toggle" dropdown-toggle>Settings <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/#/roles">Roles</a></li>
|
||||
<li><a href="/#/users">Users</a></li>
|
||||
<li><a href="/#/domains">Domains</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul ng-show="!currentUser.username" class="nav navbar-nav navbar-right">
|
||||
<li><a href="/#/login">Login</a></li>
|
||||
@ -63,7 +70,7 @@
|
||||
<ul ng-show="currentUser.username" class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown" dropdown on-toggle="toggled(open)">
|
||||
<a href class="dropdown-toggle profile-nav" dropdown-toggle>
|
||||
{{ currentUser.username }}<img ng-show="currentUser.profileImage" src="{{ currentUser.profileImage }}" class="profile img-circle">
|
||||
{{ currentUser.username }}<img ng-if="currentUser.profileImage" src="{{ currentUser.profileImage }}" class="profile img-circle">
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="logout()">Logout</a></li>
|
||||
|
117
lemur/tests/test_notifications.py
Normal file
117
lemur/tests/test_notifications.py
Normal file
@ -0,0 +1,117 @@
|
||||
from lemur.notifications.service import * # noqa
|
||||
from lemur.notifications.views import * # noqa
|
||||
|
||||
|
||||
def test_crud(session):
|
||||
notification = create('testnotify', 'email-notification', {}, 'notify1', [])
|
||||
assert notification.id > 0
|
||||
|
||||
notification = update(notification.id, 'testnotify2', {}, 'notify2', [])
|
||||
assert notification.label == 'testnotify2'
|
||||
|
||||
assert len(get_all()) == 1
|
||||
|
||||
delete(1)
|
||||
assert len(get_all()) == 0
|
||||
|
||||
|
||||
def test_notification_get(client):
|
||||
assert client.get(api.url_for(Notifications, notification_id=1)).status_code == 401
|
||||
|
||||
|
||||
def test_notification_post(client):
|
||||
assert client.post(api.url_for(Notifications, notification_id=1), data={}).status_code == 405
|
||||
|
||||
|
||||
def test_notification_put(client):
|
||||
assert client.put(api.url_for(Notifications, notification_id=1), data={}).status_code == 401
|
||||
|
||||
|
||||
def test_notification_delete(client):
|
||||
assert client.delete(api.url_for(Notifications, notification_id=1)).status_code == 401
|
||||
|
||||
|
||||
def test_notification_patch(client):
|
||||
assert client.patch(api.url_for(Notifications, notification_id=1), data={}).status_code == 405
|
||||
|
||||
|
||||
VALID_USER_HEADER_TOKEN = {
|
||||
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
|
||||
|
||||
|
||||
def test_auth_notification_get(client):
|
||||
assert client.get(api.url_for(Notifications, notification_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||
|
||||
|
||||
def test_auth_notification_post_(client):
|
||||
assert client.post(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
||||
|
||||
|
||||
def test_auth_notification_put(client):
|
||||
assert client.put(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
|
||||
|
||||
|
||||
def test_auth_notification_delete(client):
|
||||
assert client.delete(api.url_for(Notifications, notification_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||
|
||||
|
||||
def test_auth_notification_patch(client):
|
||||
assert client.patch(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
||||
|
||||
|
||||
VALID_ADMIN_HEADER_TOKEN = {
|
||||
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
|
||||
|
||||
|
||||
def test_admin_notification_get(client):
|
||||
assert client.get(api.url_for(Notifications, notification_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
||||
|
||||
|
||||
def test_admin_notification_post(client):
|
||||
assert client.post(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
||||
|
||||
|
||||
def test_admin_notification_put(client):
|
||||
assert client.put(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
|
||||
|
||||
|
||||
def test_admin_notification_delete(client):
|
||||
assert client.delete(api.url_for(Notifications, notification_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
||||
|
||||
|
||||
def test_admin_notification_patch(client):
|
||||
assert client.patch(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
||||
|
||||
|
||||
def test_notifications_get(client):
|
||||
assert client.get(api.url_for(NotificationsList)).status_code == 401
|
||||
|
||||
|
||||
def test_notifications_post(client):
|
||||
assert client.post(api.url_for(NotificationsList), data={}).status_code == 401
|
||||
|
||||
|
||||
def test_notifications_put(client):
|
||||
assert client.put(api.url_for(NotificationsList), data={}).status_code == 405
|
||||
|
||||
|
||||
def test_notifications_delete(client):
|
||||
assert client.delete(api.url_for(NotificationsList)).status_code == 405
|
||||
|
||||
|
||||
def test_notifications_patch(client):
|
||||
assert client.patch(api.url_for(NotificationsList), data={}).status_code == 405
|
||||
|
||||
|
||||
def test_auth_notifications_get(client):
|
||||
assert client.get(api.url_for(NotificationsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||
|
||||
|
||||
def test_auth_notifications_post(client):
|
||||
assert client.post(api.url_for(NotificationsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
|
||||
|
||||
|
||||
def test_admin_notifications_get(client):
|
||||
resp = client.get(api.url_for(NotificationsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json == {'items': [], 'total': 0}
|
1
setup.py
1
setup.py
@ -138,6 +138,7 @@ setup(
|
||||
'cloudca_source = lemur.plugins.lemur_cloudca.plugin:CloudCASourcePlugin'
|
||||
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
|
||||
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin'
|
||||
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin'
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
|
Loading…
Reference in New Issue
Block a user