commit
8066d540e0
|
@ -7,31 +7,22 @@
|
||||||
"""
|
"""
|
||||||
import base64
|
import base64
|
||||||
from builtins import str
|
from builtins import str
|
||||||
|
|
||||||
from flask import Blueprint, make_response, jsonify
|
from flask import Blueprint, make_response, jsonify
|
||||||
from flask.ext.restful import reqparse, Api, fields
|
from flask.ext.restful import reqparse, Api, fields
|
||||||
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
||||||
from lemur.certificates import service
|
from lemur.certificates import service
|
||||||
from lemur.authorities.models import Authority
|
from lemur.authorities.models import Authority
|
||||||
|
|
||||||
from lemur.auth.service import AuthenticatedResource
|
from lemur.auth.service import AuthenticatedResource
|
||||||
from lemur.auth.permissions import ViewKeyPermission, AuthorityPermission, UpdateCertificatePermission
|
from lemur.auth.permissions import ViewKeyPermission, AuthorityPermission, UpdateCertificatePermission
|
||||||
|
|
||||||
from lemur.roles import service as role_service
|
from lemur.roles import service as role_service
|
||||||
|
|
||||||
from lemur.common.utils import marshal_items, paginated_parser
|
from lemur.common.utils import marshal_items, paginated_parser
|
||||||
|
|
||||||
from lemur.notifications.views import notification_list
|
from lemur.notifications.views import notification_list
|
||||||
|
|
||||||
|
|
||||||
mod = Blueprint('certificates', __name__)
|
mod = Blueprint('certificates', __name__)
|
||||||
api = Api(mod)
|
api = Api(mod)
|
||||||
|
|
||||||
|
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
'name': fields.String,
|
'name': fields.String,
|
||||||
'id': fields.Integer,
|
'id': fields.Integer,
|
||||||
|
@ -104,6 +95,7 @@ def private_key_str(value, name):
|
||||||
|
|
||||||
class CertificatesList(AuthenticatedResource):
|
class CertificatesList(AuthenticatedResource):
|
||||||
""" Defines the 'certificates' endpoint """
|
""" Defines the 'certificates' endpoint """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(CertificatesList, self).__init__()
|
super(CertificatesList, self).__init__()
|
||||||
|
@ -354,6 +346,7 @@ class CertificatesList(AuthenticatedResource):
|
||||||
|
|
||||||
class CertificatesUpload(AuthenticatedResource):
|
class CertificatesUpload(AuthenticatedResource):
|
||||||
""" Defines the 'certificates' upload endpoint """
|
""" Defines the 'certificates' upload endpoint """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(CertificatesUpload, self).__init__()
|
super(CertificatesUpload, self).__init__()
|
||||||
|
@ -442,6 +435,7 @@ class CertificatesUpload(AuthenticatedResource):
|
||||||
|
|
||||||
class CertificatesStats(AuthenticatedResource):
|
class CertificatesStats(AuthenticatedResource):
|
||||||
""" Defines the 'certificates' stats endpoint """
|
""" Defines the 'certificates' stats endpoint """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(CertificatesStats, self).__init__()
|
super(CertificatesStats, self).__init__()
|
||||||
|
@ -646,6 +640,7 @@ class Certificates(AuthenticatedResource):
|
||||||
|
|
||||||
class NotificationCertificatesList(AuthenticatedResource):
|
class NotificationCertificatesList(AuthenticatedResource):
|
||||||
""" Defines the 'certificates' endpoint """
|
""" Defines the 'certificates' endpoint """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(NotificationCertificatesList, self).__init__()
|
super(NotificationCertificatesList, self).__init__()
|
||||||
|
@ -795,6 +790,38 @@ class CertificateExport(AuthenticatedResource):
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"export": {
|
||||||
|
"plugin": {
|
||||||
|
"pluginOptions": [{
|
||||||
|
"available": ["Java Key Store (JKS)"],
|
||||||
|
"required": true,
|
||||||
|
"type": "select",
|
||||||
|
"name": "type",
|
||||||
|
"helpMessage": "Choose the format you wish to export",
|
||||||
|
"value": "Java Key Store (JKS)"
|
||||||
|
}, {
|
||||||
|
"required": false,
|
||||||
|
"type": "str",
|
||||||
|
"name": "passphrase",
|
||||||
|
"validation": "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$",
|
||||||
|
"helpMessage": "If no passphrase is given one will be generated for you, we highly recommend this. Minimum length is 8."
|
||||||
|
}, {
|
||||||
|
"required": false,
|
||||||
|
"type": "str",
|
||||||
|
"name": "alias",
|
||||||
|
"helpMessage": "Enter the alias you wish to use for the keystore."
|
||||||
|
}],
|
||||||
|
"version": "unknown",
|
||||||
|
"description": "Attempts to generate a JKS keystore or truststore",
|
||||||
|
"title": "Java",
|
||||||
|
"author": "Kevin Glisson",
|
||||||
|
"type": "export",
|
||||||
|
"slug": "java-export"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
|
||||||
|
@ -804,6 +831,11 @@ class CertificateExport(AuthenticatedResource):
|
||||||
Vary: Accept
|
Vary: Accept
|
||||||
Content-Type: text/javascript
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"data": "base64encodedstring",
|
||||||
|
"passphrase": "UAWOHW#&@_%!tnwmxh832025",
|
||||||
|
"extension": "jks"
|
||||||
|
}
|
||||||
|
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
|
@ -831,5 +863,7 @@ api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificate
|
||||||
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
||||||
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
||||||
api.add_resource(CertificateExport, '/certificates/<int:certificate_id>/export', endpoint='exportCertificate')
|
api.add_resource(CertificateExport, '/certificates/<int:certificate_id>/export', endpoint='exportCertificate')
|
||||||
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates')
|
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates',
|
||||||
api.add_resource(CertificatesReplacementsList, '/certificates/<int:certificate_id>/replacements', endpoint='replacements')
|
endpoint='notificationCertificates')
|
||||||
|
api.add_resource(CertificatesReplacementsList, '/certificates/<int:certificate_id>/replacements',
|
||||||
|
endpoint='replacements')
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">
|
||||||
|
<h3 class="modal-header">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.export.plugin" ng-options="plugin.title for plugin in plugins" required></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-repeat="item in certificate.export.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}">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-form>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div ng-show="passphrase">
|
||||||
|
<h3>Successfully exported!</h3>
|
||||||
|
<h4>You're 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>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">
|
||||||
|
Replaces
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" ng-model="certificate.selectedReplacement" placeholder="Certificate123..."
|
||||||
|
typeahead="certificate.name for certificate in certificateService.findCertificatesByName($viewValue)" typeahead-loading="loadingCertificates"
|
||||||
|
class="form-control input-md" typeahead-on-select="certificate.attachReplacement($item)" typeahead-min-wait="100"
|
||||||
|
tooltip="Lemur will mark any certificates being replaced as 'inactive'"
|
||||||
|
tooltip-trigger="focus" tooltip-placement="top">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button ng-model="replacements.show" class="btn btn-md btn-default" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||||
|
<span class="badge">{{ certificate.replacements.length || 0 }}</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<tr ng-repeat="replacement in certificate.replacements track by $index">
|
||||||
|
<td><a class="btn btn-sm btn-info">{{ replacement.name }}</a></td>
|
||||||
|
<td><span class="text-muted">{{ replacement.description }}</span></td>
|
||||||
|
<td>
|
||||||
|
<button type="button" ng-click="certificate.removeReplacement($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue