Adds the ability to clone existing certificates. (#513)
This commit is contained in:
parent
a616310eb7
commit
6fd47edbe3
|
@ -148,6 +148,19 @@ class Certificate(db.Model):
|
|||
else_=False
|
||||
)
|
||||
|
||||
@property
|
||||
def extensions(self):
|
||||
# TODO pull the OU, O, CN, etc + other extensions.
|
||||
names = [{'name_type': 'DNSName', 'value': x.name} for x in self.domains]
|
||||
|
||||
extensions = {
|
||||
'sub_alt_names': {
|
||||
'names': names
|
||||
}
|
||||
}
|
||||
|
||||
return extensions
|
||||
|
||||
def get_arn(self, account_number):
|
||||
"""
|
||||
Generate a valid AWS IAM arn
|
||||
|
|
|
@ -110,9 +110,17 @@ class CertificateNestedOutputSchema(LemurOutputSchema):
|
|||
chain = fields.String()
|
||||
description = fields.String()
|
||||
name = fields.String()
|
||||
|
||||
# Note aliasing is the first step in deprecating these fields.
|
||||
cn = fields.String()
|
||||
common_name = fields.String(attribute='cn')
|
||||
|
||||
not_after = fields.DateTime()
|
||||
validity_end = ArrowDateTime(attribute='not_after')
|
||||
|
||||
not_before = fields.DateTime()
|
||||
validity_start = ArrowDateTime(attribute='not_before')
|
||||
|
||||
owner = fields.Email()
|
||||
status = fields.Boolean()
|
||||
creator = fields.Nested(UserNestedOutputSchema)
|
||||
|
@ -127,8 +135,6 @@ class CertificateCloneSchema(LemurOutputSchema):
|
|||
|
||||
class CertificateOutputSchema(LemurOutputSchema):
|
||||
id = fields.Integer()
|
||||
active = fields.Boolean()
|
||||
notify = fields.Boolean()
|
||||
bits = fields.Integer()
|
||||
body = fields.String()
|
||||
chain = fields.String()
|
||||
|
@ -136,15 +142,31 @@ class CertificateOutputSchema(LemurOutputSchema):
|
|||
description = fields.String()
|
||||
issuer = fields.String()
|
||||
name = fields.String()
|
||||
|
||||
# Note aliasing is the first step in deprecating these fields.
|
||||
notify = fields.Boolean()
|
||||
active = fields.Boolean(attribute='notify')
|
||||
|
||||
cn = fields.String()
|
||||
common_name = fields.String(attribute='cn')
|
||||
|
||||
not_after = fields.DateTime()
|
||||
validity_end = ArrowDateTime(attribute='not_after')
|
||||
|
||||
not_before = fields.DateTime()
|
||||
validity_start = ArrowDateTime(attribute='not_before')
|
||||
|
||||
owner = fields.Email()
|
||||
san = fields.Boolean()
|
||||
serial = fields.String()
|
||||
signing_algorithm = fields.String()
|
||||
|
||||
status = fields.Boolean()
|
||||
user = fields.Nested(UserNestedOutputSchema)
|
||||
|
||||
extensions = fields.Nested(ExtensionSchema)
|
||||
|
||||
# associated objects
|
||||
domains = fields.Nested(DomainNestedOutputSchema, many=True)
|
||||
destinations = fields.Nested(DestinationNestedOutputSchema, many=True)
|
||||
notifications = fields.Nested(NotificationNestedOutputSchema, many=True)
|
||||
|
|
|
@ -208,4 +208,131 @@ angular.module('lemur')
|
|||
$scope.authorityService = AuthorityService;
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
})
|
||||
|
||||
.controller('CertificateCloneController', function ($scope, $uibModalInstance, CertificateApi, CertificateService, DestinationService, AuthorityService, AuthorityApi, PluginService, MomentService, WizardHandler, LemurRestangular, NotificationService, toaster, editId) {
|
||||
CertificateApi.get(editId).then(function (certificate) {
|
||||
$scope.certificate = certificate;
|
||||
$scope.certificate.name = ''; // we should prefer the generated name.
|
||||
CertificateService.getDefaults($scope.certificate);
|
||||
});
|
||||
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
$scope.getAuthoritiesByName = function (value) {
|
||||
return AuthorityService.findAuthorityByName(value).then(function (authorities) {
|
||||
$scope.authorities = authorities;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.dateOptions = {
|
||||
formatYear: 'yy',
|
||||
maxDate: new Date(2020, 5, 22),
|
||||
minDate: new Date(),
|
||||
startingDay: 1
|
||||
};
|
||||
|
||||
|
||||
$scope.open1 = function() {
|
||||
$scope.popup1.opened = true;
|
||||
};
|
||||
|
||||
$scope.open2 = function() {
|
||||
$scope.popup2.opened = true;
|
||||
};
|
||||
|
||||
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
|
||||
$scope.format = $scope.formats[0];
|
||||
$scope.altInputFormats = ['M!/d!/yyyy'];
|
||||
|
||||
$scope.popup1 = {
|
||||
opened: false
|
||||
};
|
||||
|
||||
$scope.popup2 = {
|
||||
opened: false
|
||||
};
|
||||
|
||||
$scope.clearDates = function () {
|
||||
$scope.certificate.validityStart = null;
|
||||
$scope.certificate.validityEnd = null;
|
||||
$scope.certificate.validityYears = null;
|
||||
};
|
||||
|
||||
$scope.create = function (certificate) {
|
||||
WizardHandler.wizard().context.loading = true;
|
||||
CertificateService.create(certificate).then(
|
||||
function () {
|
||||
toaster.pop({
|
||||
type: 'success',
|
||||
title: certificate.name,
|
||||
body: 'Successfully created!'
|
||||
});
|
||||
$uibModalInstance.close();
|
||||
},
|
||||
function (response) {
|
||||
toaster.pop({
|
||||
type: 'error',
|
||||
title: certificate.name,
|
||||
body: 'lemur-bad-request',
|
||||
bodyOutputType: 'directive',
|
||||
directiveData: response.data,
|
||||
timeout: 100000
|
||||
});
|
||||
|
||||
WizardHandler.wizard().context.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.templates = [
|
||||
{
|
||||
'name': 'Client Certificate',
|
||||
'description': '',
|
||||
'extensions': {
|
||||
'basicConstraints': {},
|
||||
'keyUsage': {
|
||||
'isCritical': true,
|
||||
'useDigitalSignature': true
|
||||
},
|
||||
'extendedKeyUsage': {
|
||||
'isCritical': true,
|
||||
'useClientAuthentication': true
|
||||
},
|
||||
'subjectKeyIdentifier': {
|
||||
'includeSKI': true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Server Certificate',
|
||||
'description': '',
|
||||
'extensions' : {
|
||||
'basicConstraints': {},
|
||||
'keyUsage': {
|
||||
'isCritical': true,
|
||||
'useKeyEncipherment': true,
|
||||
'useDigitalSignature': true
|
||||
},
|
||||
'extendedKeyUsage': {
|
||||
'isCritical': true,
|
||||
'useServerAuthentication': true
|
||||
},
|
||||
'subjectKeyIdentifier': {
|
||||
'includeSKI': true
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
PluginService.getByType('destination').then(function (plugins) {
|
||||
$scope.plugins = plugins;
|
||||
});
|
||||
|
||||
$scope.certificateService = CertificateService;
|
||||
$scope.authorityService = AuthorityService;
|
||||
$scope.destinationService = DestinationService;
|
||||
$scope.notificationService = NotificationService;
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h3 class="modal-title"><span ng-show="!certificate.id">Create</span><span ng-show="certificate.id">Edit</span> Certificate <span class="text-muted"><small>encrypt all the things</small></span></h3>
|
||||
<h3 class="modal-title">Create Certificate <span class="text-muted"><small>encrypt all the things</small></span></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h3>Edit <span class="text-muted"><small>{{ certificate.name }}</small></span></h3>
|
||||
<h3 class="modal-title">Edit <span class="text-muted"><small>{{ certificate.name }}</small></span></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="editForm" class="form-horizontal" role="form" novalidate>
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
<div class="modal-header">
|
||||
<div class="modal-title">
|
||||
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h3 class="modal-header">Export <span class="text-muted"><small>{{ certificate.name }}</small></span></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<button type="button" class="close" ng-click="cancel()" aria-label="Close"><span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 class="modal-title">Export <span class="text-muted"><small>{{ certificate.name }}</small></span></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form ng-show="!passphrase" name="exportForm" class="form-horizontal" role="form" novalidate>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2">
|
||||
Plugin
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" ng-model="certificate.plugin" ng-options="plugin.title for plugin in plugins" required></select>
|
||||
<select class="form-control" ng-model="certificate.plugin"
|
||||
ng-options="plugin.title for plugin in plugins" required></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-repeat="item in certificate.plugin.pluginOptions">
|
||||
<ng-form name="subForm" class="form-horizontal" role="form" novalidate>
|
||||
<div ng-class="{'has-error': subForm.sub.$invalid, 'has-success': !subForm.sub.$invalid&&subForm.sub.$dirty}">
|
||||
<div
|
||||
ng-class="{'has-error': subForm.sub.$invalid, 'has-success': !subForm.sub.$invalid&&subForm.sub.$dirty}">
|
||||
<label class="control-label col-sm-2">
|
||||
{{ item.name | titleCase }}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="/^[0-9]{12,12}$/" class="form-control" ng-model="item.value"/>
|
||||
<select name="sub" ng-if="item.type == 'select'" class="form-control" ng-options="i for i in item.available" ng-model="item.value"></select>
|
||||
<input name="sub" ng-if="item.type == 'bool'" class="form-control" type="checkbox" ng-model="item.value">
|
||||
<input name="sub" ng-if="item.type == 'str'" type="text" class="form-control" ng-model="item.value" ng-pattern="item.validation"/>
|
||||
<p ng-show="subForm.sub.$invalid && !subForm.sub.$pristine" class="help-block">{{ item.helpMessage }}</p>
|
||||
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="/^[0-9]{12,12}$/"
|
||||
class="form-control" ng-model="item.value"/>
|
||||
<select name="sub" ng-if="item.type == 'select'" class="form-control"
|
||||
ng-options="i for i in item.available" ng-model="item.value"></select>
|
||||
<input name="sub" ng-if="item.type == 'bool'" class="form-control" type="checkbox"
|
||||
ng-model="item.value">
|
||||
<input name="sub" ng-if="item.type == 'str'" type="text" class="form-control"
|
||||
ng-model="item.value" ng-pattern="item.validation"/>
|
||||
<p ng-show="subForm.sub.$invalid && !subForm.sub.$pristine"
|
||||
class="help-block">{{ item.helpMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</ng-form>
|
||||
|
@ -35,9 +42,10 @@
|
|||
<h4>Your passphrase is: <strong>{{ passphrase }}</strong></h4>
|
||||
<p ng-show="additional">{{ additional }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" ng-show="!passphrase" ng-click="save(certificate)" ng-disabled="exportForm.$invalid" class="btn btn-success">Export</button>
|
||||
<button ng-click="cancel()" class="btn btn-danger">{{ passphrase ? "Close" : "Cancel" }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" ng-show="!passphrase" ng-click="save(certificate)" ng-disabled="exportForm.$invalid"
|
||||
class="btn btn-success">Export
|
||||
</button>
|
||||
<button ng-click="cancel()" class="btn btn-danger">{{ passphrase ? "Close" : "Cancel" }}</button>
|
||||
</div>
|
||||
|
|
|
@ -177,11 +177,25 @@ angular.module('lemur')
|
|||
|
||||
CertificateService.getDefaults = function (certificate) {
|
||||
return DefaultService.get().then(function (defaults) {
|
||||
if (!certificate.country) {
|
||||
certificate.country = defaults.country;
|
||||
}
|
||||
|
||||
if (!certificate.state) {
|
||||
certificate.state = defaults.state;
|
||||
}
|
||||
|
||||
if (!certificate.location) {
|
||||
certificate.location = defaults.location;
|
||||
}
|
||||
|
||||
if (!certificate.organization) {
|
||||
certificate.organization = defaults.organization;
|
||||
}
|
||||
|
||||
if (!certificate.organizationalUnit) {
|
||||
certificate.organizationalUnit = defaults.organizationalUnit;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ angular.module('lemur')
|
|||
body: 'Unable to update! ' + response.data.message,
|
||||
timeout: 100000
|
||||
});
|
||||
certificate.active = false;
|
||||
certificate.notify = false;
|
||||
});
|
||||
};
|
||||
$scope.getCertificateStatus = function () {
|
||||
|
@ -120,6 +120,25 @@ angular.module('lemur')
|
|||
});
|
||||
};
|
||||
|
||||
$scope.clone = function (certificateId) {
|
||||
var uibModalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
controller: 'CertificateCloneController',
|
||||
templateUrl: '/angular/certificates/certificate/certificateWizard.tpl.html',
|
||||
size: 'lg',
|
||||
backdrop: 'static',
|
||||
resolve: {
|
||||
editId: function () {
|
||||
return certificateId;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
uibModalInstance.result.then(function () {
|
||||
$scope.certificateTable.reload();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.edit = function (certificateId) {
|
||||
var uibModalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
|
|
|
@ -42,16 +42,23 @@
|
|||
<td data-title="'Common Name'" filter="{ 'cn': 'text'}">
|
||||
{{ certificate.cn }}
|
||||
</td>
|
||||
<td class="col-md-2" data-title="''">
|
||||
<div class="btn-group pull-right">
|
||||
<a class="btn btn-sm btn-default" ui-sref="certificate({name: certificate.name})">Permalink</a>
|
||||
<td data-title="''" style="text-align: center; vertical-align: middle;">
|
||||
<div class="btn-group pull-right" role="group" aria-label="...">
|
||||
<a class="btn btn-sm btn-primary" ui-sref="certificate({name: certificate.name})">Permalink</a>
|
||||
<button ng-model="certificate.toggle" class="btn btn-sm btn-info" uib-btn-checkbox btn-checkbox-true="1"
|
||||
btn-checkbox-false="0">More
|
||||
</button>
|
||||
<button ng-click="export(certificate.id)" class="btn btn-sm btn-success">
|
||||
Export
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Action
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" ng-click="edit(certificate.id)">Edit</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href ng-click="edit(certificate.id)">Edit</a></li>
|
||||
<li><a href ng-click="clone(certificate.id)">Clone</a></li>
|
||||
<li><a href ng-click="export(certificate.id)">Export</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
Loading…
Reference in New Issue