commit
07ec04ddc6
|
@ -22,7 +22,8 @@ from lemur.domains.models import Domain
|
|||
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE
|
||||
|
||||
from lemur.models import certificate_associations, certificate_source_associations, \
|
||||
certificate_destination_associations, certificate_notification_associations
|
||||
certificate_destination_associations, certificate_notification_associations, \
|
||||
certificate_replacement_associations
|
||||
|
||||
|
||||
def create_name(issuer, not_before, not_after, subject, san):
|
||||
|
@ -32,6 +33,11 @@ def create_name(issuer, not_before, not_after, subject, san):
|
|||
useful information such as Common Name, Validation dates,
|
||||
and Issuer.
|
||||
|
||||
:param san:
|
||||
:param subject:
|
||||
:param not_after:
|
||||
:param issuer:
|
||||
:param not_before:
|
||||
:rtype : str
|
||||
:return:
|
||||
"""
|
||||
|
@ -231,6 +237,11 @@ class Certificate(db.Model):
|
|||
authority_id = Column(Integer, ForeignKey('authorities.id'))
|
||||
notifications = relationship("Notification", secondary=certificate_notification_associations, backref='certificate')
|
||||
destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
|
||||
replaces = relationship("Certificate",
|
||||
secondary=certificate_replacement_associations,
|
||||
primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa
|
||||
secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa
|
||||
backref='replaced')
|
||||
sources = relationship("Source", secondary=certificate_source_associations, backref='certificate')
|
||||
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
|
||||
|
||||
|
@ -280,11 +291,44 @@ class Certificate(db.Model):
|
|||
"""
|
||||
return "arn:aws:iam::{}:server-certificate/{}".format(account_number, self.name)
|
||||
|
||||
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):
|
||||
"""
|
||||
Attempt to upload the new certificate to the new destination
|
||||
|
||||
:param target:
|
||||
:param value:
|
||||
:param initiator:
|
||||
:return:
|
||||
"""
|
||||
destination_plugin = plugins.get(value.plugin_name)
|
||||
destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options)
|
||||
|
||||
|
||||
@event.listens_for(Certificate.replaces, 'append')
|
||||
def update_replacement(target, value, initiator):
|
||||
"""
|
||||
When a certificate is marked as 'replaced' it is then marked as in-active
|
||||
|
||||
:param target:
|
||||
:param value:
|
||||
:param initiator:
|
||||
:return:
|
||||
"""
|
||||
value.active = False
|
||||
|
||||
|
||||
@event.listens_for(Certificate, 'before_update')
|
||||
def protect_active(mapper, connection, target):
|
||||
"""
|
||||
When a certificate has a replacement do not allow it to be marked as 'active'
|
||||
|
||||
:param connection:
|
||||
:param mapper:
|
||||
:param target:
|
||||
:return:
|
||||
"""
|
||||
if target.active:
|
||||
if target.replaced:
|
||||
raise Exception("Cannot mark certificate as active, certificate has been marked as replaced.")
|
||||
|
|
|
@ -76,13 +76,16 @@ def find_duplicates(cert_body):
|
|||
return Certificate.query.filter_by(body=cert_body).all()
|
||||
|
||||
|
||||
def update(cert_id, owner, description, active, destinations, notifications):
|
||||
def update(cert_id, owner, description, active, destinations, notifications, replaces):
|
||||
"""
|
||||
Updates a certificate.
|
||||
|
||||
Updates a certificate
|
||||
:param cert_id:
|
||||
:param owner:
|
||||
:param description:
|
||||
:param active:
|
||||
:param destinations:
|
||||
:param notifications:
|
||||
:param replaces:
|
||||
:return:
|
||||
"""
|
||||
from lemur.notifications import service as notification_service
|
||||
|
@ -104,6 +107,7 @@ def update(cert_id, owner, description, active, destinations, notifications):
|
|||
cert.notifications = new_notifications
|
||||
|
||||
database.update_list(cert, 'destinations', Destination, destinations)
|
||||
database.update_list(cert, 'replaces', Certificate, replaces)
|
||||
|
||||
cert.owner = owner
|
||||
|
||||
|
@ -165,6 +169,7 @@ def import_certificate(**kwargs):
|
|||
|
||||
notification_name = 'DEFAULT_SECURITY'
|
||||
notifications = notification_service.create_default_expiration_notifications(notification_name, current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL'))
|
||||
database.update_list(cert, 'replaces', Certificate, kwargs['replacements'])
|
||||
cert.notifications = notifications
|
||||
|
||||
cert = database.create(cert)
|
||||
|
@ -194,8 +199,8 @@ def upload(**kwargs):
|
|||
g.user.certificates.append(cert)
|
||||
|
||||
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
||||
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
database.update_list(cert, 'replaces', Certificate, kwargs['replacements'])
|
||||
|
||||
# create default notifications for this certificate if none are provided
|
||||
notifications = []
|
||||
|
@ -228,7 +233,7 @@ def create(**kwargs):
|
|||
# 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, 'destinations', Destination, kwargs.get('destinations'))
|
||||
|
||||
database.update_list(cert, 'replaces', Certificate, kwargs['replacements'])
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
|
||||
# create default notifications for this certificate if none are provided
|
||||
|
|
|
@ -269,7 +269,10 @@ class CertificatesList(AuthenticatedResource):
|
|||
},
|
||||
"commonName": "test",
|
||||
"validityStart": "2015-06-05T07:00:00.000Z",
|
||||
"validityEnd": "2015-06-16T07:00:00.000Z"
|
||||
"validityEnd": "2015-06-16T07:00:00.000Z",
|
||||
"replacements": [
|
||||
{'id': 123}
|
||||
]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
@ -317,6 +320,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('notifications', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('replacements', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
|
||||
self.reqparse.add_argument('validityEnd', type=str, location='json') # TODO validate
|
||||
self.reqparse.add_argument('authority', type=valid_authority, location='json', required=True)
|
||||
|
@ -375,6 +379,7 @@ class CertificatesUpload(AuthenticatedResource):
|
|||
"privateKey": "---Begin Private..."
|
||||
"destinations": [],
|
||||
"notifications": [],
|
||||
"replacements": [],
|
||||
"name": "cert1"
|
||||
}
|
||||
|
||||
|
@ -419,8 +424,9 @@ class CertificatesUpload(AuthenticatedResource):
|
|||
self.reqparse.add_argument('owner', type=str, required=True, location='json')
|
||||
self.reqparse.add_argument('name', type=str, 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('destinations', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('notifications', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('replacements', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('intermediateCert', type=pem_str, dest='intermediate_cert', location='json')
|
||||
self.reqparse.add_argument('privateKey', type=private_key_str, dest='private_key', location='json')
|
||||
|
||||
|
@ -575,7 +581,8 @@ class Certificates(AuthenticatedResource):
|
|||
"owner": "jimbob@example.com",
|
||||
"active": false
|
||||
"notifications": [],
|
||||
"destinations": []
|
||||
"destinations": [],
|
||||
"replacements": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
@ -614,6 +621,7 @@ class Certificates(AuthenticatedResource):
|
|||
self.reqparse.add_argument('description', type=str, location='json')
|
||||
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('notifications', type=notification_list, default=[], location='json')
|
||||
self.reqparse.add_argument('replacements', type=list, default=[], location='json')
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
cert = service.get(certificate_id)
|
||||
|
@ -628,7 +636,8 @@ class Certificates(AuthenticatedResource):
|
|||
args['description'],
|
||||
args['active'],
|
||||
args['destinations'],
|
||||
args['notifications']
|
||||
args['notifications'],
|
||||
args['replacements']
|
||||
)
|
||||
|
||||
return dict(message='You are not authorized to update this certificate'), 403
|
||||
|
@ -711,9 +720,65 @@ class NotificationCertificatesList(AuthenticatedResource):
|
|||
return service.render(args)
|
||||
|
||||
|
||||
class CertificatesReplacementsList(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(CertificatesReplacementsList, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, certificate_id):
|
||||
"""
|
||||
.. http:get:: /certificates/1/replacements
|
||||
|
||||
One certificate
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /certificates/1/replacements 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,
|
||||
"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",
|
||||
"signingAlgorithm": "sha2",
|
||||
"cn": "example.com",
|
||||
"status": "unknown"
|
||||
}]
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
:statuscode 403: unauthenticated
|
||||
"""
|
||||
return service.get(certificate_id).replaces
|
||||
|
||||
|
||||
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')
|
||||
api.add_resource(CertificatesReplacementsList, '/certificates/<int:certificate_id>/replacements', endpoint='replacements')
|
||||
|
|
|
@ -36,6 +36,14 @@ certificate_notification_associations = db.Table('certificate_notification_assoc
|
|||
Column('certificate_id', Integer,
|
||||
ForeignKey('certificates.id', ondelete='cascade'))
|
||||
)
|
||||
|
||||
certificate_replacement_associations = db.Table('certificate_replacement_associations',
|
||||
Column('replaced_certificate_id', Integer,
|
||||
ForeignKey('certificates.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'))
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
</td>
|
||||
<td data-title="''">
|
||||
<div class="btn-group-vertical pull-right">
|
||||
<div class="btn-group pull-right">
|
||||
<a class="btn btn-sm btn-default" ui-sref="authority({name: authority.name})">Permalink</a>
|
||||
<button tooltip="Edit Authority" ng-click="edit(authority.id)" class="btn btn-sm btn-info">
|
||||
Edit
|
||||
|
|
|
@ -5,6 +5,7 @@ angular.module('lemur')
|
|||
CertificateApi.get(editId).then(function (certificate) {
|
||||
CertificateService.getNotifications(certificate);
|
||||
CertificateService.getDestinations(certificate);
|
||||
CertificateService.getReplacements(certificate);
|
||||
$scope.certificate = certificate;
|
||||
});
|
||||
|
||||
|
@ -32,6 +33,7 @@ angular.module('lemur')
|
|||
});
|
||||
};
|
||||
|
||||
$scope.certificateService = CertificateService;
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
})
|
||||
|
@ -123,6 +125,7 @@ angular.module('lemur')
|
|||
$scope.plugins = plugins;
|
||||
});
|
||||
|
||||
$scope.certificateService = CertificateService;
|
||||
$scope.authorityService = AuthorityService;
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<p ng-show="editForm.description.$invalid && !editForm.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 ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||
</form>
|
||||
|
|
|
@ -1,84 +1,118 @@
|
|||
<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@example.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" 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.</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="1000" 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 few panels" ng-model="certificate.commonName" placeholder="Common Name" class="form-control" ng-maxlength="64" required/>
|
||||
<p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name and it must be less than 64 characters</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" />
|
||||
<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 ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||
<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@example.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" 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.</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="1000"
|
||||
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 few panels"
|
||||
ng-model="certificate.commonName" placeholder="Common Name" class="form-control" ng-maxlength="64"
|
||||
required/>
|
||||
|
||||
<p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must
|
||||
enter a common name and it must be less than 64 characters</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"/>
|
||||
<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 ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ angular.module('lemur')
|
|||
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
$scope.certificateService = CertificateService;
|
||||
|
||||
PluginService.getByType('destination').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
class="help-block">Enter a valid certificate.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-include="'angular/certificates/certificate/replaces.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||
</form>
|
||||
|
|
|
@ -67,6 +67,16 @@ angular.module('lemur')
|
|||
removeDestination: function (index) {
|
||||
this.destinations.splice(index, 1);
|
||||
},
|
||||
attachReplacement: function (replacement) {
|
||||
this.selectedReplacement = null;
|
||||
if (this.replacements === undefined) {
|
||||
this.replacements = [];
|
||||
}
|
||||
this.replacements.push(replacement);
|
||||
},
|
||||
removeReplacement: function (index) {
|
||||
this.replacements.splice(index, 1);
|
||||
},
|
||||
attachNotification: function (notification) {
|
||||
this.selectedNotification = null;
|
||||
if (this.notifications === undefined) {
|
||||
|
@ -149,6 +159,12 @@ angular.module('lemur')
|
|||
});
|
||||
};
|
||||
|
||||
CertificateService.getReplacements = function (certificate) {
|
||||
return certificate.getList('replacements').then(function (replacements) {
|
||||
certificate.replacements = replacements;
|
||||
});
|
||||
};
|
||||
|
||||
CertificateService.getDefaults = function (certificate) {
|
||||
return DefaultService.get().then(function (defaults) {
|
||||
certificate.country = defaults.country;
|
||||
|
|
|
@ -36,6 +36,7 @@ angular.module('lemur')
|
|||
CertificateService.getDomains(certificate);
|
||||
CertificateService.getDestinations(certificate);
|
||||
CertificateService.getNotifications(certificate);
|
||||
CertificateService.getReplacements(certificate);
|
||||
CertificateService.getAuthority(certificate);
|
||||
CertificateService.getCreator(certificate);
|
||||
});
|
||||
|
@ -101,6 +102,7 @@ angular.module('lemur')
|
|||
body: 'Unable to update! ' + response.data.message,
|
||||
timeout: 100000
|
||||
});
|
||||
certificate.active = false;
|
||||
});
|
||||
};
|
||||
$scope.getCertificateStatus = function () {
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
<tr ng-class="{'even-row': $even }" ng-repeat-start="certificate in $data track by $index">
|
||||
<td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">
|
||||
<ul class="list-unstyled">
|
||||
<li>{{ certificate.name }}</li>
|
||||
<li><span class="text-muted">{{ certificate.owner }}</span></li>
|
||||
<li>{{ ::certificate.name }}</li>
|
||||
<li><span class="text-muted">{{ ::certificate.owner }}</span></li>
|
||||
</ul>
|
||||
</td>
|
||||
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getCertificateStatus()">
|
||||
|
@ -37,10 +37,10 @@
|
|||
</form>
|
||||
</td>
|
||||
<td data-title="'Issuer'" sortable="'issuer'" filter="{ 'issuer': 'text' }">
|
||||
{{ certificate.authority.name || certificate.issuer }}
|
||||
{{ ::certificate.authority.name || certificate.issuer }}
|
||||
</td>
|
||||
<td data-title="'Common Name'" filter="{ 'cn': 'text'}">
|
||||
{{ certificate.cn }}
|
||||
{{ ::certificate.cn }}
|
||||
</td>
|
||||
<td class="col-md-2" data-title="''">
|
||||
<div class="btn-group pull-right">
|
||||
|
@ -61,19 +61,19 @@
|
|||
<li class="list-group-item">
|
||||
<strong>Creator</strong>
|
||||
<span class="pull-right">
|
||||
{{ certificate.creator.email }}
|
||||
{{ ::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 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 class="pull-right" tooltip="{{ ::certificate.notAfter }}">
|
||||
{{ ::momentService.createMoment(certificate.notAfter) }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
|
@ -85,15 +85,15 @@
|
|||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Bits</strong>
|
||||
<span class="pull-right">{{ certificate.bits }}</span>
|
||||
<span class="pull-right">{{ ::certificate.bits }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Signing Algorithm</strong>
|
||||
<span class="pull-right">{{ certificate.signingAlgorithm }}</span>
|
||||
<span class="pull-right">{{ ::certificate.signingAlgorithm }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Serial</strong>
|
||||
<span class="pull-right">{{ certificate.serial }}</span>
|
||||
<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"
|
||||
|
@ -107,7 +107,7 @@
|
|||
</li>
|
||||
<li class="list-group-item">
|
||||
<strong>Description</strong>
|
||||
<p>{{ certificate.description }}</p>
|
||||
<p>{{ ::certificate.description }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
|
@ -115,8 +115,8 @@
|
|||
<tab-heading>Notifications</tab-heading>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" ng-repeat="notification in certificate.notifications">
|
||||
<strong>{{ notification.label }}</strong>
|
||||
<span class="pull-right">{{ notification.description}}</span>
|
||||
<strong>{{ ::notification.label }}</strong>
|
||||
<span class="pull-right">{{ ::notification.description}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
|
@ -124,18 +124,27 @@
|
|||
<tab-heading>Destinations</tab-heading>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" ng-repeat="destination in certificate.destinations">
|
||||
<strong>{{ destination.label }}</strong>
|
||||
<span class="pull-right">{{ destination.description }}</span>
|
||||
<strong>{{ ::destination.label }}</strong>
|
||||
<span class="pull-right">{{ ::destination.description }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
<tab>
|
||||
<tab-heading>Domains</tab-heading>
|
||||
<div class="list-group">
|
||||
<a href="#/domains/{{ domain.id }}" class="list-group-item"
|
||||
ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
|
||||
<a href="#/domains/{{ ::domain.id }}" class="list-group-item"
|
||||
ng-repeat="domain in certificate.domains">{{ ::domain.name }}</a>
|
||||
</div>
|
||||
</tab>
|
||||
<tab>
|
||||
<tab-heading>Replaces</tab-heading>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" ng-repeat="replacement in certificate.replacements">
|
||||
<strong>{{ ::replacement.name }}</strong>
|
||||
<p>{{ ::replacement.description}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</tab>
|
||||
</tabset>
|
||||
<tabset justified="true" class="col-md-6">
|
||||
<tab>
|
||||
|
@ -145,7 +154,7 @@
|
|||
tooltip="Copy chain to clipboard" tooltip-trigger="mouseenter" clipboard
|
||||
text="certificate.chain"></button>
|
||||
</tab-heading>
|
||||
<pre style="width: 100%">{{ certificate.chain }}</pre>
|
||||
<pre style="width: 100%">{{ ::certificate.chain }}</pre>
|
||||
</tab>
|
||||
<tab>
|
||||
<tab-heading>
|
||||
|
@ -154,7 +163,7 @@
|
|||
tooltip="Copy certificate to clipboard" tooltip-trigger="mouseenter" clipboard
|
||||
text="certificate.body"></button>
|
||||
</tab-heading>
|
||||
<pre style="width: 100%">{{ certificate.body }}</pre>
|
||||
<pre style="width: 100%">{{ ::certificate.body }}</pre>
|
||||
</tab>
|
||||
<tab ng-click="loadPrivateKey(certificate)">
|
||||
<tab-heading>
|
||||
|
@ -163,7 +172,7 @@
|
|||
tooltip="Copy key to clipboard" tooltip-trigger="mouseenter" clipboard
|
||||
text="certificate.privateKey"></button>
|
||||
</tab-heading>
|
||||
<pre style="width: 100%">{{ certificate.privateKey }}</pre>
|
||||
<pre style="width: 100%">{{ ::certificate.privateKey }}</pre>
|
||||
</tab>
|
||||
</tabset>
|
||||
</td>
|
||||
|
|
Loading…
Reference in New Issue